Introduction to Delegates |
Written by Mike James | ||||||
Wednesday, 19 May 2010 | ||||||
Page 2 of 2
Signatures and methodsAt this point it is worth making clear that the delegate’s signature always determines how the method that the delegate wraps is called.
That is, you always have to invoke the delegate with the parameter types specified and the delegate always returns the type specified. However, is it possible to create instances of the delegate that encapsulate methods that don’t have exactly the specified signature. So to summarise:
Notice that the encapsulated method can be a static or an instance method. The Target property stores the instance and the Method property stores the method that that the delegate encapsulates. If the delegate encapsulates a static method then Target is null. To encapsulate a method on a specific object you simply have to qualify the method name with the object’s name e.g. this.hello or MyObject.hello. Covariance and contravaranceIn the simplest case a method’s signature and return type have to match the signature of the delegate type that encapsulates it. However, there is more flexibility in how a method signature can match a delegate type signature. In the documentation this is called covariance and contravariance - just to make is sound more sophisticated. Put simply covariance allows the method to return a sub-class or derived type of the return type defined in the delegate. Assume for the moment that MyType1 is the base class and MyType2 is the derived class, that is: public class MyType1 {}; If you now define the delegate type: delegate MyType1 HelloType(int param); then clearly a method returning MyType1 matches the signature but so does any method returning a type that inherits from MyType1, e.g. MyType2. So the delegate type can encapsulate a method that returns a MyType2. But following the rule that the delegate signature determines its invocation, the return type is always treated as MyType1. If the method does return a derived type, MyType2 say, you have to use a cast to work with the result as a MyType2 object. Contravariance allows the method to have parameters that are base types of the delegate type that encapsulates it. That is, if you define the delegate type: delegate void HelloType(MyType2 param); then a method that that has a parameter that is a base class for MyType2, e.g. MyType1, matches the signature. Once again you can only invoke the delegate by passing a MyType2 object but this all works because the method can always treat this as a more primitive type, e.g. as a MyType1 object. Notice that covariance and contravariance go in opposite directions - as they should to honour their original use and meaning. An output type can be more that it is supposed to be because the client recieving it and making use of it treats it as a lesser type i.e. it can be a derived type. An input type can be less than it is supposed to be i.e. if a method treats a derived type as the base type then everything is still ok - a parameter can be a base type. Generic delegatesTo confuse matters even more, or should that be to confer further elegance and power, you can also create generic delegate types. Put simply you can use a generic type anywhere within a delegate definition. For example: delegate int HelloType<T>(T param); creates a delegate type with a generic parameter. To use the delegate you have to provide the type information. For example: HelloType<int> HelloInst = Where Hello is a method which returns an int and has a single int parameter. You can also use the shorter: HelloType<int> HelloInst = hello; Before you start to invent clever ways of creating generic delegates for every purpose, I should warn you that in the main generic delegates should only be used within generic classes. In this context they provide a way to create delegate instances that “fit in” with the functioning of the entire delegate class. For example, you might create a delegate as a notification or event method which can be customised to work with the same data types as the rest of the generic class. Anonymous methods and Lambda expressionsAs of .NET 2.0 you can save some typing and names by using anonymous, as opposed to named, methods. However as of .NET 3.5 anonymous methods are superseded by lambda expression. In both cases you can simply treat the new facilities as easier ways of defining a delegate. Anonymous methods are covered in depth in another article as are Lambda Expressions. Also of interest is the way an anonymous method or Lambda expression enforces closure, another topic we have covered.
<ASIN:0321658701> <ASIN:0321637003> <ASIN:0596800959> <ASIN:047043452X> <ASIN:0123745144> |
||||||
Last Updated ( Thursday, 07 January 2016 ) |