TOP KEK
TOP KEK

Reputation: 2661

Contravariant delegate output result

I defined my class hierarchy as Object->Fruit->Apple

class Fruit : object{}

class Apple : Fruit{}

And created two static methods to work with these classes

    static Fruit FruitProcessor(string fruit)
    {
        return new Fruit();
    }

    static Apple ApplesProcessor(string apple)
    {
        return new Apple();
    }

Now I declare a delegate without any in, out keywords

public delegate TResult Funk<T, TResult>(T arg);

And in my code I do the following assignment:

        Funk<string, Fruit> myFunc;
        myFunc = FruitProcessor; // ok, we match signature exactly
        myFunc = ApplesProcessor;// this should not work, but works

Since I did not declare TResult as covariant out parameter it should not be possible to assign ApplesProcessor to myFunc delegate. But it is possible, program compiles and executes without any errors.

And if I change Funk signature to add out TResult

public delegate TResult Funk<T, out TResult>(T arg);

Everything works in the same way as before.

How is it possible?

Upvotes: 0

Views: 82

Answers (2)

D Stanley
D Stanley

Reputation: 152614

According to MSDN covariance for delegate return types and contravariance for delegate input types is implied since .NET 3.5. So adding in and out to your delegate makes no difference.

One reason this was added was so that you could assign a "vanilla" event handler that accepts an EventArgs parameter to multiple events that may use more specific classes that derive from EventArgs. For example you can use the same delegate to handle a button click and a key press, even though the former passes a MouseEventArgs parameter while the latter passes a KeyEventArgs parameter.

Upvotes: 2

Luaan
Luaan

Reputation: 63772

Case like this is handled by the usual implicit conversion. Quoting the relevant MSDN page (https://msdn.microsoft.com/en-us/library/dd233060.aspx):

public class First { }
public class Second : First { }
public delegate First SampleDelegate(Second a);
public delegate R SampleGenericDelegate<A, R>(A a);

// Matching signature. 
public static First ASecondRFirst(Second first)
{ return new First(); }

// The return type is more derived. 
public static Second ASecondRSecond(Second second)
{ return new Second(); }

// The argument type is less derived. 
public static First AFirstRFirst(First first)
{ return new First(); }

// The return type is more derived  
// and the argument type is less derived. 
public static Second AFirstRSecond(First first)
{ return new Second(); }

// Assigning a method with a matching signature  
// to a non-generic delegate. No conversion is necessary.
SampleDelegate dNonGeneric = ASecondRFirst;
// Assigning a method with a more derived return type  
// and less derived argument type to a non-generic delegate. 
// The implicit conversion is used.
SampleDelegate dNonGenericConversion = AFirstRSecond;

// Assigning a method with a matching signature to a generic delegate. 
// No conversion is necessary.
SampleGenericDelegate<Second, First> dGeneric = ASecondRFirst;
// Assigning a method with a more derived return type  
// and less derived argument type to a generic delegate. 
// The implicit conversion is used.
SampleGenericDelegate<Second, First> dGenericConversion = AFirstRSecond;

In short, in your exact case, you are assigning a non-generic delegate to a generic delegate - the implicit conversion is always used in that case. To actually make your code fail, you need to do something like this:

Funk<string, Fruit> myFunc;
Funk<string, Apple> myAppleFunc = ApplesProcessor;
myFunc = FruitProcessor;
myFunc = myAppleFunc; // Undefined implicit conversion on generic delegate

Upvotes: 2

Related Questions