【问题标题】:In Contravariance, what does it mean for a reference's assignment compatibility to be reversed?在逆变器中,引用的分配兼容性被反转意味着什么?
【发布时间】:2018-06-01 14:21:37
【问题描述】:

在逆变中,引用的赋值兼容性被颠倒是什么意思

协变和逆变(C#)

// Assignment compatibility.   
string str = "test";  
// An object of a more derived type is assigned to an object of a less derived type.   
object obj = str;  

// Covariance.   
IEnumerable<string> strings = new List<string>();  
// An object that is instantiated with a more derived type argument   
// is assigned to an object instantiated with a less derived type argument.   
// Assignment compatibility is preserved.   
IEnumerable<object> objects = strings;  

// Contravariance.             
// Assume that the following method is in the class:   
// static void SetObject(object o) { }   
Action<object> actObject = SetObject;  
// An object that is instantiated with a less derived type argument   
// is assigned to an object instantiated with a more derived type argument.   
// Assignment compatibility is reversed.   
Action<string> actString = actObject;

参考: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/

此外,当尝试从派生较少的类型转换为派生较多的类型时,会抛出InvalidCastException。由于不允许用户定义的转换到基类或从基类,我看不出方法组的逆变是如何工作的——不会/不应该调用这样的方法抛出一个@ 987654328@也是?


更新:https://blogs.msdn.microsoft.com/ericlippert/2009/11/30/whats-the-difference-between-covariance-and-assignment-compatibility/

【问题讨论】:

  • 我不明白你的问题。您要求/表现得好像 允许逆变 是必须在 C# 语言的规则内 实现的东西,当然不是这样。语言的规则已更改以允许逆变发生。
  • @Damien_The_Unbeliever 我已经阅读了几篇关于它的介绍如何潜在地破坏代码的文章(csharp.2000things.com/tag/assignment-compatibilityblogs.msdn.microsoft.com/ericlippert/2009/03/19/…csharpindepth.com/Articles/General/Overloading.aspx
  • 逆变是观察到,如果一个参数应该是一个object,那么它当然也可以是一个string(这是一个object),并且代表应该适应因此,每个Action&lt;object&gt; 都可以被视为Action&lt;string&gt;,因为Action 不会询问string,而不是询问任何旧的object。所涉及的转换是从stringobject 的微不足道的转换,而不是相反,并且不涉及用户定义的转换。
  • (object &lt; string) =&gt; (Action&lt;string&gt; &lt; Action&lt;object&gt;)(in T),因为Action&lt;object&gt; 是赋值兼容的(表示保留),在参数T 中,与(可以存储到)Action&lt;string&gt;。因此,Action&lt;T&gt; 在 T 上是逆变的。

标签: c# .net contravariance


【解决方案1】:

赋值兼容性被颠倒了意味着您可以将派生程度较低的类型分配给派生程度较高的类型。

在示例中,逆变器与 Action 类一起显示。 Action&lt;object&gt; 可以分配给Action&lt;string&gt;

此代码有效:

public void Test(object o)
{
    Console.WriteLine(o.GetType().ToString());
}

Action<string> foo;
foo = Test;
foo.Invoke("bar");

在此示例中,我们可以看到字符串 "bar" 在作为参数传递给Test 方法时被隐式转换为对象类型。不涉及无效转换或用户定义的转换。

Action 不是协变的,因此不能将Action&lt;string&gt; 分配给Action&lt;object&gt;,尝试这样做会引发异常。

当泛型类型是输入参数并且与输出参数协变时,泛型类型是逆变的。 所以Func 类与它的第一种类型是协变的,并且与它的第二种(和第三种,...)相反:

  • Func&lt;string, object&gt; 可以分配给Func&lt;string, string&gt;
  • Func&lt;string, string&gt; 可以分配给Func&lt;object, string&gt;
  • Func&lt;string, object&gt; 可以分配给Func&lt;object, string&gt;
  • Func&lt;object, string&gt; 不能分配给 Func&lt;string, string&gt;

要在 C# 中声明逆变泛型类型,我们可以使用 in 关键字声明逆变接口。在这样做时,我们只能使用泛型类型作为输入参数:

interface IMyGenericType<in T>
{
    object Test(T input); //allowed
    //T GetValue(); //not allowed in a contravariant interface
}

使用这种类型,我可以将IMyGenericType&lt;object&gt; 分配给IMyGenericType&lt;string&gt;

【讨论】:

  • 那么,对于我问题的第二部分,我的事情颠倒了......在你的第一个例子中,如果Test 采用了string,而是与((Action&lt;object&gt;)Test).Invoke(new object()) 协变调用,那么不会不是抛出异常吗?
  • @sαmosΛris:LINQPad 是您快速测试此类事物的朋友。 Action&lt;string&gt; Test = s =&gt; Console.WriteLine(s); ((Action&lt;object&gt;)Test).Invoke(new object()); -> Unable to cast object of type 'System.Action`1[System.String]' to type 'System.Action`1[System.Object].
  • @JeroenMostert 但是,这与 op 示例中使用的协方差类型不同吗 (IEnumerable&lt;string&gt; strings = new List&lt;string&gt;(); IEnumerable&lt;object&gt; objects = strings;)?
  • @sαmosΛris IEnumerable 是协变的,而 Action 是逆变的,并且与逆变分配兼容性是相反的。
  • @sαmosΛris:比较Func&lt;string&gt; Test = () =&gt; ""; ((Func&lt;object&gt;)Test).Invoke();——没有错误。参数是逆变的,返回值是协变的,因此在接口规范上inout。这完全取决于接口如何使用类型。
【解决方案2】:

您可以查看 Action https://msdn.microsoft.com/en-us/library/018hxwa8(v=vs.110).aspx 参考,它说 T 参数是通过 in 传递的,这意味着它是逆变的。

 static object GetObject() { return null; }  
 static void SetObject(object obj) { }  

static string GetString() { return ""; }  
static void SetString(string str) { }  

static void Test()  
{  
    // Covariance. A delegate specifies a return type as object,  
    // but you can assign a method that returns a string.  
    Func<object> del = GetString;  

    // Contravariance. A delegate specifies a parameter type as string,  
    // but you can assign a method that takes an object.  
    Action<string> del2 = SetObject;  
}  

在这个例子中,逆变是显而易见的,并且它可以工作,因为你将传递给 del2 的任何字符串都可以成为对象。所以逆变应该在实现逆变解决方案合乎逻辑的地方实现,比如 Action 委托类型参数,另一个例子是 IComparer 接口。

【讨论】:

    猜你喜欢
    • 2019-02-21
    • 2020-05-21
    • 2016-02-20
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多