The twenty-third day of learning c#

Table of contents

C# Delegate

Delegation overview

Use delegates

Delegates with named methods and anonymous methods

Example

How to merge delegates (multicast delegates)

Example

How to declare, instantiate, and use delegates

Example

Reliable programming


C# Delegate

Delegate is a reference type that represents a reference to a method with a specific parameter list and return type. When instantiating a delegate, you can associate its instance with any method with a compatible signature and return type. You can call methods through delegate instances.

Delegates are used to pass methods as parameters to other methods. Event handlers are methods called through delegates. You can create a custom method that a class (such as a Windows control) can call when a specific event occurs. The following example demonstrates a delegate declaration:

public delegate int PerformCalculation(int x, int y);

Any method in any accessible class or structure that matches the delegate type can be assigned to a delegate. The method can be a static method or an instance method. This flexibility means you can programmatically change method calls and insert new code into existing classes.

Note:In the context of method overloading, the method's signature does not include a return value. But in the context of a delegate, the signature includes the return value. In other words, the method and delegate must have the same return type.

The ability to reference methods as parameters makes delegates ideal for defining callback methods. You can write a method that compares two objects in your application. This method can be used in the delegate of the sorting algorithm. Since the comparison code is separated from the library, the sorting method may be more common.

Function pointers have been added to C# 9 for similar scenarios where you need more control over the calling convention. Call the code associated with the delegate using virtual methods added to the delegate type. Using function pointers, different conventions can be specified.

Delegation overview

Delegates have the following properties:

  1. Similar to C++ function pointers, but completely object-oriented. Unlike C++ function pointers, delegates encapsulate both object instances and methods.
  2. Allowing methods to be passed as parameters makes delegates very flexible and can be used to define callback methods.
  3. Multiple delegates can be chained together, for example, multiple methods can be called on an event to achieve the functionality of a multicast delegate.
  4. The method does not have to match the delegate type exactly, which means variants can be used to accommodate different method signatures. For more information, seeUsing Variations in Delegates.
  5. Use Lambda expressions to write inline code blocks more concisely. In some contexts, lambda expressions can be compiled into delegate types, further simplifying code writing. To learn more about lambda expressions, see lambda expressions.

In general, delegation, as an important feature in C#, provides developers with a very flexible and powerful method for handling scenarios such as callbacks, event processing, and dynamic method invocations.

Use delegates

Delegate is a type that safely encapsulates methods, similar to function pointers in C and C++. Unlike C function pointers, delegates are object-oriented, type-safe, and reliable. The type of delegate is determined by the delegate's name. The following example declares a delegate named Callback , which can encapsulate a string as a parameter and return  void method:

public delegate void Callback(string message);

A delegate object can usually be constructed in two ways, by providing the name of the method the delegate will encapsulate, or by using a lambda expression< a i=2>. After a delegate is instantiated in this way, it can be called. Calling a delegate invokes methods attached to the delegate instance. The parameters passed by the caller to the delegate are passed to the method, and the delegate returns the method's return value (if any) to the caller. For example:

// 为委托创建方法
public static void DelegateMethod(string message)
{
    Console.WriteLine(message);
}
// 实例化委托。
Callback handler = DelegateMethod;

// 使用委托。
handler("Hello World");

The delegate type is derived from the Delegate class in .NET. Delegate types are sealed; they cannot be derived from Delegate nor Derive a custom class from it. Since an instantiated delegate is an object, it can be passed as an argument or assigned to a property. This allows methods to accept a delegate as a parameter and call the delegate later. This is called an asynchronous callback and is a common way to notify the caller when a long process is complete. When using delegates in this way, the code using the delegate does not need to know which implementation method to use. The functionality is similar to that provided by the encapsulated interface.

Another common use of callbacks is to define a custom comparison method and pass that delegate to the short method. It allows the caller's code to become part of the sorting algorithm. The following example method uses the Del type as a parameter:

public static void MethodWithCallback(int param1, int param2, Callback callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}

You can then pass the delegate created above to the method:

MethodWithCallback(1, 2, handler);

and receive the following output to the console:

The number is: 3

When using delegates in an abstract way, MethodWithCallback does not need to call the console directly, remember, it does not have to be designed to have a console. What MethodWithCallback does is simply prepare a string and pass it to other methods. This feature is particularly powerful since a delegate's methods can take any number of parameters.

When a delegate is constructed to encapsulate an instance method, the delegate references both the instance and the method. A delegate has no knowledge of the instance type other than the methods it encapsulates, so a delegate can reference any type of object as long as there is a method on that object that matches the delegate's signature. When a delegate is constructed to encapsulate a static method, the delegate only references the method. Consider the following statement:

public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}

Together with the static DelegateMethod shown earlier, we now have three methods that Del instances can encapsulate.

When called, a delegate can call multiple methods. This is called multicast. To add additional methods to a delegate's method list (invocation list), simply add two delegates using the addition operator or the additive assignment operator ("+" or "+="). For example:

var obj = new MethodClass();
Callback d1 = obj.Method1;
Callback d2 = obj.Method2;
Callback d3 = DelegateMethod;

//这两种类型的分配都是有效的
Callback allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

At this time, the call list of allMethodsDelegate contains three methods, namely Method1, Method2 and DelegateMethod. The original three delegates (d1, d2, and d3) remain unchanged. When allMethodsDelegate is called, all three methods are called in sequence. If the delegate uses reference parameters, the references will be passed to all three methods in reverse order, and any changes made by one method will be seen on the other method. When a method throws an exception that is not caught within the method, the exception is passed to the caller of the delegate and subsequent methods in the call list are not called. If the delegate has a return value and/or output parameters, it will return the return value and parameters of the last method called. To remove a method from the call list, use thesubtraction operator or the subtractive assignment operator (- or -=). For example:

//remove Method1
allMethodsDelegate -= d1;

// 删除 d2 时复制 AllMethodsDelegate
Callback oneMethodDelegate = allMethodsDelegate - d2;

Because a delegate type derives from System.Delegate, the methods and properties defined by that class can be called on the delegate. For example, to query the number of methods in a list of delegate calls, you could write:

int invocationCount = d1.GetInvocationList().GetLength(0);

Delegates with multiple methods in the call list are derived from  MulticastDelegate, which is a subclass of System.Delegate. Since both classes support GetInvocationList, the above code will work in other cases as well.

Multicast delegates are widely used in event handling. The event source object sends event notifications to receiver objects that are registered to receive the event. To register for an event, the receiver needs to create a method to handle the event, then create a delegate for the method and pass the delegate to the event source. When an event occurs, the source calls the delegate. The delegate then calls the event handling method on the receiver, providing the event data. The delegate type for a given event is determined by the event source. For more information, seeEvents.

Comparing two assigned delegates of different types at compile time will result in a compilation error. If the delegate instance is of static System.Delegate type, the comparison is allowed but will return false at runtime. For example:

delegate void Callback1();
delegate void Callback2();

static void method(Callback1 d, Callback2 e, System.Delegate f)
{
    // Compile-time error.
    //Console.WriteLine(d == e);

    // OK at compile-time. False if the run-time type of f
    // is not the same as that of d.
    Console.WriteLine(d == f);
}

Delegates with named methods and anonymous methods

Delegate can be associated with named methods. When instantiating a delegate using a named method, the method is passed as a parameter, for example:

// 声明一个委托
delegate void WorkCallback(int x);

// 定义命名方法
void DoWork(int k) { /* ... */ }

// 使用方法作为参数实例化委托
WorkCallback d = obj.DoWork;

This is called using a named method. Delegates constructed using named methods can encapsulate static methods or instance methods. Named methods were the only way to instantiate a delegate in earlier versions of C#. However, if creating a new method would cause unnecessary overhead, C# allows you to instantiate a delegate and immediately specify the block of code that the delegate will handle when the delegate is called. Code blocks can contain Lambda expressions or anonymous methods.

Methods passed as delegate parameters must have the same signature as the delegate declaration. Delegate instances can encapsulate static methods or instance methods.

Note:Although the delegate can use the out parameter, it is not recommended to use this delegate with multicast Used with event delegates because you have no way of knowing which delegate will be called.

Starting in C# 10, a method group containing a single overload has a natural type. This means that the compiler can infer the return type and parameter types of the delegate type:

var read = Console.Read; // 只有一个过载;Func<int>推断
var write = Console.Write; // 错误:多个重载,无法选择

Example

using System;

public delegate void MyDelegate(string message);

public class Program
{
    public static void Main()
    {
        // 使用命名方法创建委托实例
        MyDelegate delegate1 = new MyDelegate(Method1);
        delegate1("来自命名方法的Hello");

        // 使用匿名方法创建委托实例
        MyDelegate delegate2 = delegate (string message)
        {
            Console.WriteLine("匿名方法: " + message);
        };

        delegate2("来自匿名方法的Hello");
    }

    // 命名方法
    public static void Method1(string message)
    {
        Console.WriteLine("方法1: " + message);
    }
}

In this example, we first define a delegate type named MyDelegate, which accepts a string parameter and returns no value. Then, in the Main method, we create two delegate instances delegate1 and delegate2.

Among them, delegate1 uses the named method Method1 to initialize, and calls delegate1 to trigger the delegate, thereby calling the associated named method.

delegate2 uses an anonymous method to initialize, and calls delegate2 to trigger the delegate, thereby calling the associated anonymous method.

How to merge delegates (multicast delegates)

Use the + operator to assign multiple delegate objects to a delegate instance, thereby creating a multicast delegate. This multicast delegate contains a list of assigned delegates. When the multicast delegate is called, the delegates in the list will be called in sequence.

Only delegate instances of the same type can be merged by using the + operator, otherwise a compilation error will occur.

Additionally, the - operator can also be used to remove a component delegate from a multicast delegate. For example, if we have a multicast delegate delegateChain that contains two delegate objects delegate1 and delegate2, we can use the - operator to remove one of the delegate objects from the multicast delegate:

delegateChain = delegateChain - delegate1; // 从多播委托中删除 delegate1

This will remove delegate1 from delegateChain so that delegateChain only contains delegate2 delegate objects.

Example

using System;

public delegate void MyDelegate(string message);

class Program
{
    static void Main()
    {
        MyDelegate delegate1 = new MyDelegate(Method1);
        MyDelegate delegate2 = new MyDelegate(Method2);
        MyDelegate delegate3 = new MyDelegate(Method3);

        // 合并多个委托对象到一个多播委托
        MyDelegate delegateChain = delegate1 + delegate2 + delegate3;

        // 调用多播委托,将依次调用列表中的委托
        delegateChain("第一次依次调用列表中的委托");

        Console.WriteLine();

        // 从多播委托中删除一个委托对象
        delegateChain = delegateChain - delegate2;

        // 再次调用多播委托,将不再调用已删除的委托对象
        delegateChain("第二次依次调用列表中的委托");
    }

    static void Method1(string message)
    {
        Console.WriteLine("Method1: " + message);
    }

    static void Method2(string message)
    {
        Console.WriteLine("Method2: " + message);
    }

    static void Method3(string message)
    {
        Console.WriteLine("Method3: " + message);
    }
}

The running results are as follows:

Method1: 第一次依次调用列表中的委托
Method2: 第一次依次调用列表中的委托
Method3: 第一次依次调用列表中的委托

Method1: 第二次依次调用列表中的委托
Method3: 第二次依次调用列表中的委托

In this example, we have defined three different delegate objects delegate1, delegate2 and delegate3, all pointing to different methods. We combine them into a multicast delegate delegateChain using the + operator and call it to call the delegates in the list in sequence.

We then removed a delegate object delegate2 from delegateChain using the - operator. When delegateChain is called again, the deleted delegate object delegate2 is no longer called.

How to declare, instantiate, and use delegates

Delegates can be declared using any of the following methods:

  • Declare the delegate type and declare the method with a matching signature.
  • Assign method groups to delegate types.
  • Declare anonymous methods.
  • Use lambda expression. For more information, see Lambda Expressions.

Here is sample code using different methods to declare a delegate:

using System;

// 1. 使用匹配签名声明委托类型并声明方法
public delegate void MyDelegate(string message);

class Program
{
    static void Main()
    {
        // 2. 将方法分配给委托类型
        MyDelegate delegate1 = new MyDelegate(Method1);
        delegate1("将方法分配给委托类型");

        // 3. 声明匿名方法并赋给委托
        MyDelegate delegate2 = delegate (string message) { Console.WriteLine("匿名方法: " + message); };
        delegate2("声明匿名方法并赋给委托");

        // 4. 使用 lambda 表达式
        MyDelegate delegate3 = (message) => { Console.WriteLine("Lambda表达式: " + message); };
        delegate3("使用 lambda 表达式");
    }

    static void Method1(string message)
    {
        Console.WriteLine("Method1: " + message);
    }
}

In this example, we demonstrate four different ways to declare delegate and assign methods:

  1. We first declare a delegate of type MyDelegate using a matching signature, and declare a method Method1 whose signature matches the delegate type.
  2. Then, we assign the named method Method1 to the delegate1 delegate object and call it.
  3. Next, we declare an anonymous method, assign it to the delegate2 delegate object, and call it.
  4. Finally, we declare a method using a lambda expression, assign it to the delegate3 delegate object, and call it.

Example

Here is a simple example showing how to declare, instantiate and use delegates in C#:

using System;

// 声明一个委托类型,它接受两个整数参数并返回一个整数
public delegate int MyDelegate(int x, int y);

class Program
{
    static void Main()
    {
        // 实例化委托对象并将其分配给命名方法
        MyDelegate delegate1 = new MyDelegate(Method1);

        // 使用委托对象调用已分配的方法
        int result1 = delegate1(10, 5);
        Console.WriteLine("结果1: " + result1);

        // 重新分配委托对象到匿名方法
        MyDelegate delegate2 = delegate (int x, int y) { return x - y; };

        // 使用委托对象调用已分配的匿名方法
        int result2 = delegate2(10, 5);
        Console.WriteLine("结果2: " + result2);

        // 重新分配委托对象到 lambda 表达式
        MyDelegate delegate3 = (x, y) => x * y;

        // 使用委托对象调用已分配的 lambda 表达式
        int result3 = delegate3(10, 5);
        Console.WriteLine("结果3: " + result3);
    }

    // 命名方法,它与 MyDelegate 委托类型具有相同的签名
    static int Method1(int x, int y)
    {
        return x + y;
    }
}

In this example, we first declare a delegate type MyDelegate that accepts two integer parameters and returns an integer. We then instantiated three delegate objects and assigned them to different methods, anonymous methods, and lambda expressions. We use these delegate objects to call assigned methods, anonymous methods, and lambda expressions, and print the results.

Reliable programming

1. Declare the delegation type:

public delegate void ProcessBookCallback(Book book);

Each delegate type describes the number and types of arguments, as well as the return value types of the methods it can encapsulate. Whenever a new set of argument types or return value types is required, a new delegate type must be declared.

2. Instantiate delegation:

After declaring a delegate type, you must create a delegate object and associate it with a specific method. In the above example, you do this by passing the PrintTitle method to the ProcessPaperbackBooks method, as shown in the following example:

bookDB.ProcessPaperbackBooks(PrintTitle);

This will create a new delegate object associated with the static method Test.PrintTitle. Likewise, as shown in the following example, pass the non-static method AddBookToTotal in the object totaller:

bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

In both cases, pass a new delegate object to the ProcessPaperbackBooks method. Once a delegate is created, the methods associated with it never change; delegate objects are immutable.

3. Call the delegate

After you create a delegate object, you typically pass the delegate object to other code that will call the delegate. A delegate object is invoked by using the name of the delegate object, followed by the arguments in parentheses that will be passed to the delegate. Here is an example of a delegate call:

processBook(b);

It is important to note that delegates can be called synchronously or asynchronously, depending on the needs and design of the application. Asynchronous calls can be achieved by using the BeginInvoke and EndInvoke methods.

Guess you like

Origin blog.csdn.net/m0_74293254/article/details/134527521