【问题标题】:Assigning IEnumerable (Covariance)分配 IEnumerable(协方差)
【发布时间】:2012-10-18 19:37:06
【问题描述】:

由于IEnumerable 在 C# 4.0 中有一个协变参数,我对它在以下代码中的行为感到困惑。

public class Test
{
    IEnumerable<IFoo> foos;

    public void DoTestOne<H>(IEnumerable<H> bars) where H : IFoo
    {
        foos = bars;
    }

    public void DoTestTwo(IEnumerable<IBar> bars)
    {
        foos = bars;
    }
}
public interface IFoo
{
}
public interface IBar : IFoo
{
}

所以基本上DoTestOne 方法不能编译,而DoTestTwo 可以。除了为什么不起作用之外,如果有人知道我如何实现DoTestOne 的效果(将IEnumberable&lt;H&gt; where H : IFoo 分配给IEnumberable&lt;IFoo&gt;),我将不胜感激。

【问题讨论】:

    标签: c# c#-4.0 ienumerable covariance


    【解决方案1】:

    如果你知道 H 将是一个类,这确实有效:

        public void DoTestOne<H>(IEnumerable<H> bars) where H : class, IFoo
        {
            foos = bars;
        }
    

    这里的问题是,如果 H 是值类型,则协方差并不完全符合您的预期,因为 IEnumerable&lt;MyStruct&gt; 实际上返回值类型,而 IEnumerable&lt;IFoo&gt; 必须返回装箱实例。如有必要,您可以使用显式 Cast&lt;IFoo&gt; 来解决此问题。

    【讨论】:

    • 谢谢,这是我需要的缺失信息。
    【解决方案2】:

    您只需要在其中对IEnumerable&lt;IFoo&gt; 进行转换即可:

    public void DoTestOne<H>(IEnumerable<H> bars) where H : IFoo
    {
        foos = (IEnumerable<IFoo>)bars;
    }
    

    编辑由 Dan Bryant 提供:当 Hstruct 时,使用 foos = bars.Cast&lt;IFoo&gt;() 而不是上面的方法可以规避 InvalidCastException。

    【讨论】:

    • 如果你传入一些结构,这会导致运行时异常,对吧? (不是批评,只是想记住它是如何工作的。)
    • @Rawling 你说得对,我刚刚检查过——InvalidCastException。
    • @dbaseman,我实际上认为这种语法会起作用,但显然不是。不过,这个概念是合理的;只需改用.Cast&lt;IFoo&gt;()
    • @DanBryant:.Cast 方法将使用非泛型 IEnumerable,其关联的非泛型 IEnumerator 会将值类型实例复制到其对应堆对象的新实例类型返回对后一个实例的引用。仅当 IEnumerable&lt;T&gt; 的类型参数不知道是类类型时才需要使用它。在编译器知道T 是类类型还是结构类型的情况下,可以使用重载DoTestOne&lt;T&gt;(T bars) where T:IEnumerable&lt;IFoo&gt;DoTestOne&lt;T&gt;(IEnumerable&lt;T&gt; bars) where T:struct {DoTestOne(bars.Cast&lt;IFoo&gt;);}
    • @DanBryant:如果有一个IEnumerable&lt;T&gt; where T:IFoo,并且已知T 是结构或类类型,编译器将选择正确的重载。但是,如果 T 是不受约束的泛型,则编译器无法选择重载。可以编写一种方法来在运行时非常有效地进行确定,但我不知道有什么方法可以编写重载,这样后一种方法只会在前两种方法的静态类型确定失败的情况下被调用。
    【解决方案3】:

    在 .net 运行时中,每个值类型都有一个关联的同名堆对象类型。在某些情况下,将使用值类型;在其他情况下,堆类型。当声明值类型的存储位置(变量、参数、返回值、字段或数组槽)时,该存储位置将保存该类型的实际内容。当声明类类型的存储位置时,它将保存null 或对存储在其他地方的堆对象的引用。接口类型的存储位置被视为引用类型的位置,并且即使接口的部分(或全部)实现实际上是值类型,也会保存堆引用。

    尝试将值类型存储到引用类型存储位置将导致系统创建与值类型关联的堆类型的新实例,将所有字段从原始存储位置复制到相应的字段中新实例,并存储对该实例的引用,这个过程称为“装箱”。尝试将堆引用转换为值类型存储位置将检查它是否引用与值类型关联的堆类型的实例;如果是这样,堆对象的字段将被复制(“取消装箱”)到值类型存储位置中的相应字段中。

    虽然看起来像System.Int32 这样的类型派生自System.Object,但这只是对了一半。有一个堆对象类型System.Int32,它确实派生自System.Object,但System.Int32 类型的变量不包含对此类对象的引用。相反,这样的变量保存与该整数相关的实际数据; 数据本身只是位的集合,并不来自任何东西

    如果将接口类型的存储位置视为持有“从实现接口 _ 的 System.Object 派生的东西”,那么实现该接口的任何类类型的实例都是该类型的实例,但是值类型的实例——即使它们可以转换为其他类型——不是任何其他类型的实例。使用IEnumerator&lt;IFoo&gt; 的代码不仅希望它的Current 方法返回可以转换为IFoo 的东西,或者实现IFoo;它希望它返回一个Object 的衍生物,它实现了IFoo。因此,要让IEnumerable&lt;T&gt; 替代IEnumerable&lt;IFoo&gt;,必须限制T 以实现IFoo 并成为System.Object 的适当派生。

    【讨论】:

    • 对装箱实例的引用是一种引用,但所引用的类型与未装箱的值类型相同。实际上没有两种不同的 CLR 类型。此外,值类型确实是从 Object 派生的(实际上是从 ValueType 派生的)。特别是,在值类型上从 Object 派生的方法不会使用装箱实例调用;相反,您将键入的(未装箱的)引用作为“this”传递给该值。当然,这些是微妙的区别。最主要的是不要将类型的概念与不同类型的引用的概念混淆。
    • @DanBryant:在 CLR 中被指示创建 System.Int32 类型的存储位置,它创建的东西与 System.Int32 堆对象非常不同。两种事物都使用相同的Type 进行描述,但它们的行为不同。例如,将List&lt;string&gt;.Enumerator 类型的变量复制到需要IEnumerator&lt;string&gt; 的方法将传递枚举器状态的快照,而将List&lt;string&gt;.Enumerator 类型的堆对象的引用传递给这样的方法将传递一个直接(“实时”)引用状态。
    • @DanBryant:有时,某种形式的自动装箱可能会有所帮助(例如处理String.Format 的参数),但“统一类型系统”模型并不能准确匹配现实;与其将任何会暴露模型和现实之间差异的东西嘲笑为“邪恶”,我认为认识到拥有行为与类对象不同的值类型对象是有用的会更有帮助。虽然能够捕获类对象中任意值类型存储位置的 状态 很有用,但这并不意味着...
    • ...类对象和值类型的存储位置应该被认为是等价的。如果泛型从一开始就存在于 .net 中,将值类型和类类型视为存在于单独的“宇宙”中会有所帮助,但允许将值类型 T 隐式转换为类类型 ValueHolder&lt;T&gt;暴露的字段ValueValueHolder&lt;T&gt; 不会实现由T 实现的接口,但如果需要,实现IFoo 的结构T 可以定义到IFoo 的隐式转换。不过,泛型从一开始就不存在。这就是生活。
    【解决方案4】:

    您忘记了返回中的强制转换或通用约束中的“类”标识符。你的所作所为当然是可以的,参考下面。

    发件人:http://msdn.microsoft.com/en-us/library/d5x73970.aspx

    where T : <interface name>
    
    The type argument must be or implement the specified interface.
    Multiple interface constraints can be specified. The constraining interface can also be generic.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-13
      • 1970-01-01
      • 2016-08-24
      • 1970-01-01
      相关资源
      最近更新 更多