views:

126

answers:

5

Why does this not work? Do I not understand delegate covariance correctly?

public delegate void MyDelegate(object obj)

public class MyClass
{
    public MyClass()
    {
         //Error: Expected method with 'void MyDelegate(object)' signature
         _delegate = MyMethod;
    }

    private MyDelegate _delegate;

    public void MyMethod(SomeObject obj)
    {}

}
+1  A: 

The MyDelegate type declares that you can pass any kind of object in. However, MyMethod only takes objects of type SomeObject. What happens if I try to invoke the delegate passing a different kind of object: _delegate("a string object")? According to the declaration of MyDelegate, this should be allowed, but your function MyMethod can't actually receive a string argument.

JSBangs
+6  A: 

Correct - you don't understand covariance correctly - yet :) Your code would work if you had the same types but as return values, like this:

public delegate object MyDelegate()

public class MyClass
{
    public MyClass()
    {
         _delegate = MyMethod;
    }

    private MyDelegate _delegate;

    public SomeObject MyMethod() { return null; }
}

That would demonstrate covariance. Alternatively, you can keep it as parameters but switch the types around:

public delegate void MyDelegate(SomeObject obj)

public class MyClass
{
    public MyClass()
    {
         _delegate = MyMethod;
    }

    private MyDelegate _delegate;

    public void MyMethod(object obj) {}
}

This now demonstrates contravariance.

My rule of thumb is to ask myself, "given the delegate, what could I do with it? If I can pass in an argument which would break the method, the conversion should have failed. If the method can return something which would break the caller, the conversion should have failed."

In your code, you could have called:

_delegate(new object());

At that point, poor MyMethod has a parameter which is meant to be of type SomeObject, but is actually of type object. This would be a Very Bad Thing, so the compiler stops it from happening.

Does that all make more sense?

Jon Skeet
This doesn't solve the problem of the original code where a generic delegate needs to be implemented in a more specific manner. This is the whole point of Generics in the language.
Payton Byrd
I had it backwards and it makes much more sense now! The more generic type must be a parameter on the method not within the delegate. Thanks!
Adam Driscoll
@Payton Byrd: What makes you think that's the problem of the original code? I assumed that the OP wanted to know why his code didn't work, given that that's the question he asked.
Jon Skeet
@Jon SkeetUnless the intent of the original code was NOT go create a "generic" delegate that could be reused then I believe my interpretation to be correct. I've updated my answer to explain why the Generic is necessary.
Payton Byrd
@Payton Bird: I took the intent of the original code to be investigating variant method group conversions. It's quite possible to want to know more about method group conversions without having a case where generics are necessary.
Jon Skeet
+2  A: 

You need to use a generic.

EDIT: Why? Because as another poster noted, Object and SomeObject do not equate to the same thing as Object may not be SomeObject. This is the whole point of Generics in the language.

public delegate void MyDelegate<T>(T obj)

public class MyClass
{
    public MyClass()
    {
        _delegate = MyMethod;
    }

    private MyDelegate<SomeObject> _delegate;

    public void MyMethod(SomeObject obj)
    {
    }
}
Payton Byrd
You are right this was my original intention with the code! I looked into what 4.0 offers and they will allow for some pretty cool generic delegate stuff: http://blog.t-l-k.com/dot-net/2009/c-sharp-4-covariance-and-contravariance
Adam Driscoll
@Adam: You should be aware that the variance you've been looking at in your post isn't new to C# 4.0 - it's available in C# 2.0. It's only *generic* variance that's new in C# 4.
Jon Skeet
+2  A: 

Arguments are contravariant, return types are covariant. If the delegate were to be called with an object that is not an instance of SomeObject, you'd have a typing error. On the other hand, returning SomeObject from a routine wrapped in a delegate that returns object is fine.

Jeffrey Hantin
+1  A: 

From the MSDN link you provided

Covariance permits a method to have a more derived return type than what is defined in the delegate. Contravariance permits a method with parameter types that are less derived than in the delegate type.

You're attempting to use a more derived parameter type which isn't supported (although .NET 4.0 probably will since this has sorted out many covariance/contravariance issues).

Paolo
April 12th right? Soon, soon, soon...
Adam Driscoll
No, this code doesn't work in C# 4.0 either. Otherwise, you would be able to pass "object" to a method that needs "SomeObject", which is not safe.
Alexandra Rusina