使用泛型委托时如何理解逆变?

How to understand contravariance when using generic delegates?

我正在学习“逆变泛型委托”。

我的理解是:

The "in" keyword specifies that the type parameter is contravariant.
This allows for implicit conversion of delegate types.

If there is no "in" keyword, we don't know if the type parameter is contravariant.
Then implicit conversion of delegate types are not allowed.

这是我的代码:

public class Test
{
    //public delegate bool FuncDelegate<T>(T t);
    public delegate bool FuncDelegate<in T>(T t);

    public class BaseClass
    {
        public int x;
    }

    public class DerivedClass: BaseClass
    {
        public int y;
    }

    static bool BaseFunc(BaseClass bc)
    {
        if (bc.x > 1)
            return false;
        else
            return true;
    }

    static bool DerivedFunc(DerivedClass dc)
    {
        if (dc.y > 1)
            return false;
        else
            return true;
    }

    public static void Main()
    {
        FuncDelegate<DerivedClass> genericDerivedFunc = DerivedFunc;
        FuncDelegate<BaseClass> genericBaseFunc = BaseFunc;

        genericDerivedFunc = genericBaseFunc;

        FuncDelegate<DerivedClass> genericDerivedFunc2 = BaseFunc;
    }
}

我的问题

/*
    This line is valid when declared as: public delegate bool FuncDelegate<in T>(T t);
    This line is invalid when declared as: public delegate bool FuncDelegate<T>(T t);
*/
genericDerivedFunc = genericBaseFunc;

这一行符合我的理解。

/*
    This line is always valid.
*/
FuncDelegate<DerivedClass> genericDerivedFunc2 = BaseFunc;

我不明白这行:

"bool BaseFunc(BaseClass bc)" can implicitly converts to bool "FuncDelegate<DerivedClass>(DerivedClass t)".

我认为它必须有“in”关键字来指定逆变。

但转换可以在没有“in”关键字的情况下完成。

文档在 variance in delegates 部分对此进行了介绍:

.NET Framework 3.5 introduced variance support for matching method signatures with delegate types in all delegates in C#. This means that you can assign to delegates not only methods that have matching signatures, but also methods that return more derived types (covariance) or that accept parameters that have less derived types (contravariance) than that specified by the delegate type. This includes both generic and non-generic delegates.

因此,在您的情况下,这直接属于“接受派生类型(逆变)少于委托类型指定的参数”的部分。

如果你看到 sharplab 的反编译,你会看到 FuncDelegate<DerivedClass> genericDerivedFunc2 = BaseFunc; 实际上被转换成类似这样的东西:

FuncDelegate<DerivedClass> genericDerivedFunc2 = new FuncDelegate<DerivedClass>(BaseFunc);

genericDerivedFunc = genericBaseFunc; 只是 FuncDelegate<BaseClass>FuncDelegate<DerivedClass> 的简单赋值,当 FuncDelegate 不是逆变时会失败。

注意这两个作业右侧的区别:

genericDerivedFunc = genericBaseFunc;
genericDerivedFunc2 = BaseFunc;

第一行的右侧是一个 delegate,因此您正在将一个委托类型转换为另一个委托类型。这需要 variance conversion,如 C# 规范中的可用转换中所列:

The implicit reference conversions are:

  • ...
  • From any reference_type to an interface or delegate type T if it has an implicit identity or reference conversion to an interface or delegate type T0 and T0 is variance-convertible to T.

方差转换需要 inout

但是在第二行,右边是一个方法组(一个方法的名称),所以在第二行,你实际上是在做一个method group conversion. For such a conversion to be available, BaseFunc needs to be compatible 与目标委托类型。请注意,这是对方法的要求,而不是对委托类型的要求。要“兼容”。

值得注意的是,方法 M 与委托类型 D “兼容”的两个要求是:

  • For each value parameter, an identity conversion or implicit reference conversion exists from the parameter type in D to the corresponding parameter type in M.
  • An identity or implicit reference conversion exists from the return type of M to the return type of D.

这些要求使委托类型看起来好像在其所有参数上都具有 in 修饰符,在其 return 类型上具有 out 修饰符。

基本上,因为 RHS 是非常不同的东西,所以适用不同的规则。