C# 代理协方差混淆难题!

C# 代理协方差混淆难题!,c#,delegates,covariance,C#,Delegates,Covariance,为什么这不起作用?我是否正确理解了代理协方差 public delegate void MyDelegate(object obj) public class MyClass { public MyClass() { //Error: Expected method with 'void MyDelegate(object)' signature _delegate = MyMethod; } private MyDeleg

为什么这不起作用?我是否正确理解了代理协方差

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)
    {}

}

MyDelegate
类型声明可以在中传递任何类型的对象。但是,
MyMethod
只接受类型为
SomeObject
的对象。如果我试图调用传递不同类型对象的委托:
\u委托(“字符串对象”)
,会发生什么?根据
MyDelegate
的声明,这应该是允许的,但是您的函数
MyMethod
实际上不能接收字符串参数。

正确-您没有正确理解协方差-但是:)如果您具有相同的类型但作为返回值,则代码可以工作,如下所示:

public delegate object MyDelegate()

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

    private MyDelegate _delegate;

    public SomeObject MyMethod() { return null; }
}
这将证明协方差。或者,您可以将其保留为参数,但可以切换类型:

public delegate void MyDelegate(SomeObject obj)

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

    private MyDelegate _delegate;

    public void MyMethod(object obj) {}
}
这现在证明了相反的情况

我的经验法则是问我自己,“对于委托,我能用它做什么?如果我能传入一个会破坏该方法的参数,转换应该失败。如果该方法能返回一些会破坏调用方的东西,转换应该失败。”

在代码中,您可以调用:

_delegate(new object());
此时,poor
MyMethod
有一个参数,它的类型是
SomeObject
,但实际上是
object
类型。这将是一件非常糟糕的事情,因此编译器会阻止它发生


这一切更有意义吗?

您需要使用泛型

编辑:为什么?因为作为另一张海报 注意,Object和SomeObject没有 等同于同一事物作为对象可能 不是什么东西。这就是全部 语言中的泛型点

public委托void MyDelegate(T obj)
公共类MyClass
{
公共MyClass()
{
_delegate=MyMethod;
}
私人MyDelegate(私人MyDelegate);
公共void MyMethod(SomeObject obj)
{
}
}

参数是逆变的,返回类型是协变的。如果使用不是
SomeObject
实例的
对象调用委托,则会出现键入错误。另一方面,从包含在返回
对象的委托中的例程返回
SomeObject
是可以的。

从您提供的链接

协方差允许方法具有 比实际情况更衍生的返回类型 在委托中定义。 逆变法允许使用 派生较少的参数类型 而不是在委托类型中


您正试图使用不受支持的更派生的参数类型(尽管.NET 4.0可能会,因为这已经解决了许多协方差/逆变问题)。

协方差和逆变是关于理解继承的is-a原则

在这两种情况下,协方差和反方差,s。作为返回值或作为委托方法的参数“传递”。被“传递”的东西必须被“捕获”在容器中。在C#或编程术语中,我们使用bucket这个词来表示我所谓的容器。有时,为了理解常用行话的意思,你不得不求助于其他词语

无论如何,如果你理解继承,这里的读者很可能会理解,那么唯一要注意的是容器,即。E用于捕获的bucket必须是与正在传递的bucket相同的类型或派生类型更少的类型–这对于协方差和反方差都是正确的

遗产说你可以在动物桶里抓一只鸟,因为鸟是动物。所以,如果一个方法的参数必须捕捉一只鸟,你可以在一个动物桶里捕捉它(一个类型为animal的参数),这就是逆差。 如果您的方法,即您的委托返回一个bird,那么“bucket”也可以是bird类型或派生程度较低的类型(父类型),这意味着您捕获方法返回值的变量必须是与返回值相同或派生程度较低的类型

只要改变你的思维,区分正在传递的和捕获的,因为所有关于协变和逆变的复杂性都会很好地消失。然后你意识到同样的原理也在起作用。只是继承不能被违反,因为它只能单向流动


而且编译器非常聪明,当您将bucket转换为更专业化的类型时(同样,根据需要),只有这样,您才能将添加到更派生类中的所有专业化方法都取回。这就是它的美妙之处。因此,它是捕获、转换和使用您所拥有和可能需要的内容。

大量头韵总是很有趣。我试图在一篇简短的常见问题解答帖子中回答这样的问题:关于此功能,仍然存在很多困惑……这并不能解决原始代码中需要以更具体的方式实现泛型委托的问题。这就是语言中泛型的全部意义。我把它倒过来了,现在它更有意义了!更泛型的类型必须是委托之外的方法上的参数。谢谢@佩顿·伯德:是什么让你认为这是原始代码的问题?我假设OP想知道为什么他的代码不起作用,因为他问了这个问题。除非原始代码的目的不是创建一个可以重用的“通用”委托,否则我相信我的解释是正确的。我已经更新了我的答案来解释为什么泛型是必要的。@Payton Bird:我认为原始代码的目的是研究变量方法组转换。在不需要泛型的情况下,很有可能想要了解更多关于方法组转换的信息。你是对的,这是我编写代码的初衷!我研究了4.0提供的功能,它们将允许一些非常酷的通用代理内容:@Adam:你应该是awa
public delegate void MyDelegate<T>(T obj)

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

    private MyDelegate<SomeObject> _delegate;

    public void MyMethod(SomeObject obj)
    {
    }
}