C#代表中的协方差/不变性/违反.[英] Covariance/Invariance/Contravariance in delegates in C#

本文是小编为大家收集整理的关于C#代表中的协方差/不变性/违反.的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

本文来自:IT宝库(https://www.itbaoku.cn)

我有以下代码.我尚未指定任何通用参数和该委托的in/out(差异).如果我正确理解不变性的含义,我应该无法返回基本类型的对象,因为我的代表提到了对象的返回类型.

我对不变性的理解是错误的吗?

class Program
{
    public delegate object SampleDelegate(Base b);

    static void Main(string[] args)
    {
        List<Base> listBases = new List<Base>(){new Base{}, new Base{}};
        SampleDelegate newDel = new SampleDelegate(ProcessBase);
        newDel(new Base() { });
        Console.ReadLine();
    }

    public static Base ProcessBase(Base b)
    {
        return b;
    }

    public class Base
    {

    }

    public class Derived : Base
    {
    }   
}

推荐答案

如果我正确理解不变性的含义,我应该无法返回基本类型的对象,因为我的代表提到了对象的返回类型.我对不变性的理解是错误的吗?

由于您可以编译并运行该程序,因此您已经知道该问题的答案.是的.

让我们问您要问的问题:

由于代表甚至都不是通用,因此对代表的显然一般差异不适用.然后,为什么我可以将返回Base的方法进行协变转换为需要方法返回object>?

的委托类型

显然,通用协方差不是相关的协方差;这里有一个完全不同的规则.首先在C#2.0中允许此转换.当从A 方法组转换为A 委托 时,从方法组中选择的方法可以比代表的返回类型更一般的返回类型,但前提是这两种类型都是参考类型.类似于参数类型,它们是违反的.

允许使用参考类型构建的通用委托类型之间的转换的功能类似地是协变量的,而我的contravariant(顺便说一句)添加到C#4.0.

.

其他推荐答案

所有委托都允许一定程度的协方差,因为一​​个人可以为委托分配具有更派生的返回类型的方法.这就是您在这里所做的,其中您有一种返回类型Base的方法和返回类型object的委托.

从本质上讲,返回的值被施加到object上,这将始终有效,因为从Base到object的铸件始终将工作.

或以另一种方式查看它,通过类型SampleDelegate的委托来调用ProcessBase是致电(object)ProcessBase(theArgument).

您可以将其视为与我们始终可以调用具有更多派生类型的参数的方法相反的.例如.我们可以这样做ReferenceEquals("abc", 1),因为"abc"可以将其施加到object中,而1可以通过拳击.

.

确实,出于类似的原因,您可以将ProcessBase分配给定义为public delegate object SampleDelegate(Derived b);的委托,因为它总是可以安全地称呼它,因为它只能用Derived参数调用,这总是可以始终可以在通话时施放到Base.

我们经常谈论covariance和contravariance与C#中的代表时,我们的意思是协变和逆向类型参数.

(主要是因为上述差异类型自2.0以来是在语言中使用的,并且自4.0以来我将在下面描述的类型,因此后一种类型是已经在工作的C#程序员的"新闻").

如果我们有一个通用委托定义为:

public delegate TResult MyDelegate<TArg, TResult>(TArg argument);

然后考虑以下内容:

MyDelegate<Base, Derived> del = b => null;//simple example.
MyDelegate<Derived, Derived> del2 = del; // compiler error 1. CS0029: Cannot implicitly convert type
MyDelegate<Base, Base> del3 = del; // compiler error 2. CS0029: Cannot implicitly convert type
MyDelegate<Derived, Base> del4 = del; // compiler error 3. CS0029: Cannot implicitly convert type

我们不允许这两个转换中的任何一个,但是当您考虑一下时,可能会出错?

如果我们将MyDelegate的定义更改为:

public delegate TResult MyDelegate<in TArg, TResult>(TArg argument);

然后第一个错误消失了,因为in允许TArg的违反.

如果我们将定义更改为:

public delegate TResult MyDelegate<TArg, out TResult>(TArg argument);

然后第二个错误消失了,因为out允许在TResult上进行协方差.

最后,如果我们将定义更改为:

public delegate TResult MyDelegate<in TArg, out TResult>(TArg argument);

然后所有三个错误都消失了,因为我们同时拥有in和out.

协方差的规则不允许您分配任何不合逻辑.例如:

public delegate TResult MyDelegate<out TArg, in TResult>(TArg argument);

有两个错误:我们不能期望在TArg上也不安全地违反TResult>;如果我们被允许这样做,我们将被允许分配对其他委托类型的代表.

Func和Action类型提供了此示例.例如,其中一个被定义为:

public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);

因此,我们可以将a Func<object, object, string>分配给类型Func<string, string, object>的变量,因为所有涉及的调用都可以使用,但是我们不能将Func<string, string, object>分配给类型Func<object, object, string>的变量,因为这无法保持.

通用协方差和违反率也具有接口,使我们能够例如将IEnumerable<string>分配给类型IEnumerable<object>的变量,因为我们可以在IEnumerable<object>上调用的所有内容,我们可以安全地在IEnumerable<string>上拨打.

本文地址:https://www.itbaoku.cn/post/3335087.html

问题描述

I Have the following piece of code. I have not specified any generic parameters and IN/OUT(variance) for this delegate. If I understand the meaning of invariance correctly i should not be able to return object of Base type since my delegate mentions return type of object.

Is my understanding of invariance wrong?

class Program
{
    public delegate object SampleDelegate(Base b);

    static void Main(string[] args)
    {
        List<Base> listBases = new List<Base>(){new Base{}, new Base{}};
        SampleDelegate newDel = new SampleDelegate(ProcessBase);
        newDel(new Base() { });
        Console.ReadLine();
    }

    public static Base ProcessBase(Base b)
    {
        return b;
    }

    public class Base
    {

    }

    public class Derived : Base
    {
    }   
}

推荐答案

If I understand the meaning of invariance correctly i should not be able to return object of Base type since my delegate mentions return type of object. Is my understanding of invariance wrong?

Since you can compile and run that program, you already know the answer to that question. Yes.

Let's ask the question you meant to ask:

Since the delegate is not even generic, clearly generic variance on delegates does not apply. Why then can I make a covariant conversion from a method returning Base to a delegate type that requires that the method return object?

Clearly generic covariance is not the kind of covariance that is relevant; there is an entirely different rule at play here. This conversion was first allowed in C# 2.0. When converting from a method group to a delegate, the method chosen from the method group may have a return type more general than the delegate's return type, provided that both types are reference types. And similarly for the parameter types, which are contravariant.

The feature of allowing conversions between generic delegate types constructed with reference types to similarly be covariant and contravariant was added -- by me, incidentally -- to C# 4.0.

其他推荐答案

All delegates allow a degree of covariance in that one can assign a method with a more derived return type to a delegate. This is what you did here, where you had a method with return type Base and a delegate of return type object.

Essentially the value returned is cast to object on invocation, which will always work, because the cast from Base to object is always going to work.

Or to look at it another way, to call ProcessBase via a delegate of type SampleDelegate is to call (object)ProcessBase(theArgument).

You can think of this as the opposite to how we can always call a method with arguments of more derived types. E.g. we can do ReferenceEquals("abc", 1) because "abc" can be cast to object as a more derived type, and 1 can be cast to òbject` by boxing.

And indeed, for similar reasons you could assign ProcessBase to a delegate defined as public delegate object SampleDelegate(Derived b); because it would always be safe to call it, because it could only ever be called with a Derived argument, which could always be cast to Base on calling.

More often when we talk about covariance and contravariance with delegates in C#, we mean that of covariant and contravariant type parameters.

(Mainly because the type of variance described above was in the language since 2.0, and the type I'll describe below since 4.0, so the latter type was "news" to already-working C# programmers).

If we have a generic delegate defined as:

public delegate TResult MyDelegate<TArg, TResult>(TArg argument);

Then consider the following:

MyDelegate<Base, Derived> del = b => null;//simple example.
MyDelegate<Derived, Derived> del2 = del; // compiler error 1. CS0029: Cannot implicitly convert type
MyDelegate<Base, Base> del3 = del; // compiler error 2. CS0029: Cannot implicitly convert type
MyDelegate<Derived, Base> del4 = del; // compiler error 3. CS0029: Cannot implicitly convert type

We aren't allowed to do either of the two conversions, but when you think about it, what could possibly go wrong here?

If we change the definition of MyDelegate to:

public delegate TResult MyDelegate<in TArg, TResult>(TArg argument);

Then the first error goes away, because the in allows contravariance on TArg.

If we change the definition to:

public delegate TResult MyDelegate<TArg, out TResult>(TArg argument);

Then the second error goes away, because the out allows covariance on TResult.

Finally if we change the definition to:

public delegate TResult MyDelegate<in TArg, out TResult>(TArg argument);

Then all three errors go away, because we have both the in and the out.

The rules of covariance an contravariance won't allow you to assign anything illogical. E.g.:

public delegate TResult MyDelegate<out TArg, in TResult>(TArg argument);

Has two errors: We cannot expect to be safely covariant on TArg nor safely contravariant on TResult; if we were allowed to do this, we would be allowed to assign delegates that didn't work to other delegate types.

The Func and Action types provide examples of this. For example, one of them is defined as:

public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);

And hence we can assign a Func<object, object, string> to a variable of type Func<string, string, object> because all calls involved will work, but we cannot assign a Func<string, string, object> to a variable of type Func<object, object, string>, as this doesn't hold.

Generic covariance and contravariance also holds with interfaces, allowing us to e.g. assign an IEnumerable<string> to a variable of type IEnumerable<object> because everything we can call on an IEnumerable<object> we can safely call on an IEnumerable<string>.