C# Delegates and Events

With the gradual increase in use, I can’t stand the use of these concepts in the vague situation, so I found some official doc to learn these concepts in C #, mainly to distinguish between delegates and events.

In fact, I personally have been very vague about these concepts before reading the doc, and even once was confused by various blogs on the Internet, and even confused Action, Func and commission.

Finally, after I went to read the official doc, I had some understanding of these concepts.

First of all, the most important conclusion is thrown. Both delegates and events are to provide a way to post-process functions. Events are actually multicast based on delegates, and it is inconvenient to define a new delegate type every time you use a delegate, so it is provided. Two strong types of delegates are Action and Func.

What is delegation

A 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 a method through a delegate instance.

Delegates are used to pass methods as arguments 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:

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

You can assign any method in any accessible class or struct that matches the delegate type to the delegate. The method can be a static method or an instance method. This sexual aparteness allows you to programmatically change method calls and insert new code into existing classes.

The purpose of this code is to declare a new delegate. Note that it is a declaration, which is equivalent to declaring a new type. It can be understood as declaring a new class without instantiation.

The ability to reference methods as arguments makes delegates ideal for defining callback methods. A method can be written to compare two objects in an application. This method can be used in delegates for sorting algorithms. Since the comparison code is separated from the library, sorting methods may be more common.

Delegates have the following properties:

  • Delegates are similar to C++ function pointer, but delegates are fully Object Oriented, unlike C++ pointers that remember function, delegates encapsulate both object instances and methods.

  • Delegates allow methods to be passed as arguments.

  • Delegates can be used to define callback methods.

Delegates can be chained together; for example, multiple methods can be called on one event.

  • The method does not have to exactly match the delegate type. For more information, see使用委托中的变体

  • Use Lambda expressions to write internal connection Code Blocks more concisely. Lambda expressions (in some contexts) can be compiled to delegate types. To learn more about lambda expressions, see lambda 表达式

Use of delegation

委托是安全封装方法的类型,类似于 C 和 C++ 中的函数指针。 与 C 函数指针不同的是,委托是面向对象的、类型安全的和可靠的。 委托的类型由委托的名称确定

1
public delegate void Del(string message);

Delegate objects can usually be constructed in two ways, one is to provide a delegate with a method name, and the other is to use lambda 表达式When a delegate is instantiated, the call to the delegate is passed to the methodParameters passed to the delegate by the caller are passed to the method, and the delegate returns the method’s return value, if any, to the callerThis is called an invocation delegateAn instantiated delegate can be called as the encapsulated method itselfFor example:

1
2
3
4
5
6
7
8
9
10
// Create a method for a delegate.public static void DelegateMethod(string message)
public static void DelegateMethod(string message)
{
Console.WriteLine(message);
}
//Instantiate a delegate
Del handler = DelegateMethod;

// Call the delegate.
handler( Hello World );

The delegate type is derived from the Delegate Class. The delegate type is密封的, they cannot be derived from, nor can a custom class be derived from them. Since the delegate instantiated is an object, it can be passed as an argument or assigned to a property. This allows the method to accept the delegate as an argument and call the delegate later. This is called an asynchronous callback and is a common method for notifying the caller when a long process completes. When a delegate is used in this way, the code using the delegate does not need to know the implementation method to be used. Functionality is similar to that provided by the wrapper interface.

When a delegate is constructed to encapsulate an instance method, the delegate will reference both the instance and the method. The delegate does not know an instance type other than the method it encapsulates, so the delegate can reference any type of object as long as there are methods on that object that match the delegate signature. When the delegate is constructed to encapsulate a static method, the delegate only references the method. Consider the following declaration:

1
2
3
4
5
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 invoked, a delegate can call multiple methods. This is called multicast. To add additional methods to the delegate’s method list (invocation list), simply add two delegates using the addition operator or the addition assignment operator (“+” or “+=”). For example:

1
2
3
4
5
6
7
8
var obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;

//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

At this point, the invocation list for allMethodsDelegate contains three methods, Method1, Method2, and DelegateMethod. The original three delegates (d1, d2, and d3) remain unchanged. When allMethodsDelegate is called, all three methods are called in order. If the delegate uses reference parameters, references are passed to all three methods in reverse order, and any changes made by one method are seen on the other. 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 invocation list are not invoked. If the delegate has a return value and/or output parameters, it will return the return value and parameters of the last called method. To remove a method from the invocation list, use减法运算符或减法赋值运算符(Or -=)。 for example:

1
2
3
4
5
//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;

Because the delegate type is derived from System. Delegate, the methods and properties defined by the class can be called on the delegate. For example, to query the number of methods in the delegate invocation list, you can write:

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

A delegate with multiple methods in the invocation list is derived from MulticastDelegate, which is a subclass of’System. Delegate ‘. Since both classes support’GetInvocationList’, the above code will also work in other cases.

Multicast delegates are widely used in event handling. An event source object sends an event notification to a receiver object that is registered to receive the event. To register an event, the receiver needs to create a method to handle the event, then create a delegate for that method and pass the delegate to the event source. When the event occurs, the source invokes the delegate. The delegate will then call an event handling method on the receiver, providing event data. The delegate type for a given event is determined by the event source. For more information, see事件

For more usage methods, please refer to:如何声明,实例化和使用委托

Strongly typed delegates: Action, Func

The abstract Delegate class provides the infrastructure for loose coupling and invocation. Concrete delegate types become more useful by including and enforcing type safety of methods added to the invocation list of delegate objects. When the delegate keyword is used and concrete delegate types are defined, the compiler will generate these methods.

In fact, whenever a different method signature is required, this creates a new delegate type. This operation can become cumbersome after a while. Every new feature requires a new delegate type.

Fortunately, there is no need to do this. The .NET Core framework contains several types that are reusable when delegate types are needed. These are泛型Definition, so you can declare a custom when you need a new method declaration.

First type is Action Types and some variations:

1
2
3
4
public delegate void Action();
public delegate void Action<in T>(T arg);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
// Other variations removed for brevity.

Variants of the Action delegate can contain up to 16 parameters, such as ActionIt is important that these definitions use different generic parameters for each delegate parameter: this allows maximum flexibility. Method parameters are not required but may be of the same type.

Use an Action type for any delegate type that has a void return type.

This framework also includes several generic delegate types that can be used to return values.

1
2
3
4
public delegate TResult Func<out TResult>();
public delegate TResult Func<in T1, out TResult>(T1 arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
// Other variations removed for brevity

Variants of the’Func 'delegate can contain up to 16 input parameters, such as FuncBy convention, the type of the result is always the last type parameter in all Func declarations.

Use a’Func 'type for any delegate type that returns a value.

There is also a specialized type of delegation PredicateThis type returns the test result of a single value.

1
public delegate bool Predicate<in T>(T obj);

You may notice that for any Predicate type, there exists a structurally equivalent Func type, for example

1
2
Func<string, bool> TestForString;
Predicate<string> AnotherTestForString;

You may think that these two types are equivalent. They are not. These two variables cannot be used interchangeably. A variable of one type cannot be assigned to another type. The C # type system uses the name of a defined type, not its structure.

What is an event

Similar to delegates, events are * late binding * mechanisms. In fact, events are built on language support for delegates.

Events are a way for objects to broadcast (to all relevant components in the system) what has happened. Any other component can subscribe to events and be notified when they are raised.

You may have used events in some programming. Many graphics systems have event models for reporting user interactions. These events report mouse movements, button clicks, and similar interactions. This is one of the most common scenarios for using events, but not the only one.

It is possible to define events that should be raised against classes. One thing to note when using events is that a particular event may not have any registered objects. Code must be written to ensure that events are not raised when listeners are not configured.

By subscribing to events, you can also create coupling between two objects (event source and event sink). You need to ensure that the event sink will unsubscribe from the event source when you are no longer interested in the event.

Event-supported design goals

The language design of the event addresses these goals:

  • Enable very small coupling between event source and event sink. These two components may not be written by the same organization and may even be updated through completely different schedules.

  • Subscribing to an event and unsubscribing from the same event should be very simple.

  • Event source should support multiple event subscribers. It should also support not attaching any event subscribers.

You will find that the goal of the event is very similar to the goal of the delegate. Therefore, the event language support is built on the delegate language support.

Language support for events

The syntax used to define events and to subscribe or unsubscribe to them is an extension of the delegate syntax.

Define events that use the keyword’event ':

1
public event EventHandler<FileListArgs> Progress;

The type of the event (in this example, ‘EventHandler < FileListArgs >’) must be a delegate type. When declaring an event, a number of conventions should be followed. Typically, the event delegate type has an invalid return. The event declaration should be a predicate or a predicate phrase. Use the past tense when the event reports something that has already happened. Use the present tense predicate (e.g., ‘Closing’) to report something that will happen. Typically, use the present tense to indicate that the class supports some type of custom behavior. One of the most common scenarios is to support cancellation. For example, the’Closing 'event may include arguments indicating whether the shutdown operation should continue. Other scenarios may allow the caller to modify the behavior by updating the properties of the event parameter. You can raise an event to indicate the suggested next action that the algorithm will take. Event handlers can authorize different actions by modifying the properties of event parameters.

When you want to raise an event, invoke the event handler using the delegate invocation syntax:

1
Progress?.Invoke(this, new FileListArgs(file));

As委托As described in the section, the?. operator makes it easy to ensure that the event is not raised if there is no subscriber for the event.

Subscribe to events by using the ‘+=’ operator:

1
2
3
4
EventHandler<FileListArgs> onProgress = (sender, eventArgs) =>
Console.WriteLine(eventArgs.FoundFile);

fileLister.Progress += onProgress;

Handler methods are usually prefixed with “On” followed by the event name, as shown above.

Use the ‘- =’ operator to unsubscribe:

1
fileLister.Progress -= onProgress;

Be sure to declare a local variable for the expression representing the event handler. This will ensure that the handler is unsubscribed. If the body of a lambda expression is used, an attempt will be made to delete the handler that was never attached, which is an invalid operation.

For more ways to use events, such as how to cancel the execution of events, you can see:标准.NET事件模式And新的事件模式

The new event mode mainly liberalizes the restrictions of event parameters, does not have to inherit from EventArgs, and pays attention to asynchronous event handlers

Distinguish between delegates and events

They both provide a late binding scheme: in this scheme, components communicate by calling methods that are only recognized at runtime. They both support single and multiple subscriber methods. This is called unicast and multicast support. Both support similar syntax for adding and removing handlers. Finally, raising events and calling delegates use exactly the same method call syntax. They even both support the same’Invoke () ‘method syntax for use with the’?. 'operator.

Given all these similarities, it can be difficult to determine which grammar to use when

Listening for events is optional

When determining which language features to use, the most important consideration is whether you must have additional subscribers. If your code must call code provided by the subscriber, you should use a delegate-based design when you need to implement callbacks. If your code can do all its work without calling any subscribers, you should use an event-based design.

Consider the examples generated in this section. A comparer function must be provided for code generated with List. Sort () to sort elements correctly. A LINQ query must be provided with a delegate to determine which elements to return. Both use the design generated with the delegate.

Consider the’Progress’ event. It reports the progress of the task. The task will continue with or without a listener. The’FileSearcher 'is another example. It will still search and find all the files it has found, even if no event subscribers are attached. The UX controls work even if no subscribers are listening for events. They all use an event-based design.

The return value requires delegation.

Another note is the method prototypes required for delegate methods. As you can see, delegates used for events all have invalid return types. You also see that there is an idiom for creating event handlers that pass information back to the event source by modifying the properties of the event parameter object. While these idioms work, they are not as natural as returning values from methods.

Note that these two heuristic methods may often coexist: if the delegate method returns a value, it may affect the algorithm in some way.

Event has a dedicated call

Classes other than the class containing the event can only add and remove event listeners; only the class containing the event can call the event. Events are usually public class members. In contrast, delegates are usually passed as arguments and stored as private class members (if they are all stored).

Event listeners usually have a longer lifetime

The reason that event listeners generally have a longer lifetime is not very good. However, you may find that event-based design is more natural when the event source will raise events for a long period of time. Examples of event-based UX control design can be seen on many systems. After subscribing to an event, the event source may raise events for the entire lifetime of the program. (You can unsubscribe from events when they are no longer needed.)

Compare this to many delegate-based designs where the delegate is used as a parameter to a method and is no longer used after the method is returned.