Delegate Covariance Confusion Conundrum!
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;
}
p开发者_如何学编程rivate MyDelegate _delegate;
public void MyMethod(SomeObject obj)
{}
}
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?
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.
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)
{
}
}
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.
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).
Covariance and Contravariance is about understanding the Is-a-Principle of inheritance.
In both, covariance and contravariance, s.th. is "passed along", either as return value or as an argument to the delegate method. That which is "passed along" has to be "caught" in a receptacle. In C# – or programming jargon as such – we use the word bucket for what I called receptacle. Sometimes you have to fall back to other words in order to catch the meaning of commonly used jargon words.
Anyway, if you understand inheritance, which most likely any reader here will, then the only thing to pay attention to is that the receptacle, i. e. the bucket used for catching has to be of the same type or less derived type than that which is being passed – this being true for both covariance and contravariance.
Inheritance says you can catch a bird in an animal bucket because the bird is an animal. So if a parameter of a method has to catch a bird you could catch it in an animal bucket (a parameter of type animal), which then is contravariance. And if your method, i.e. your delegate returns a bird, then the "bucket" also can be a of type bird or less derived (of a parent type) meaning the variable where you catch the return value of the method has to be of the same or less derived type than the return value.
Just switch your thinking to discriminate between that which is being passed and that which catches as then all complexity about covariance and contravariance dissolves nicely. Then you realize that the same principle is at work. It is just that inheritance cannot be violated as it flows only one way.
And the compiler is so smart that when you cast the bucket in the more specialized type (again, and as need be) that then and only then you get all the specialized methods back that were added into the more derived class. That is the beauty of it. So it is catch, cast and use what you have and perhaps need.
精彩评论