【问题标题】:Why can I not assign a List of concrete types to a List of that concrete's interface?为什么我不能将具体类型的列表分配给该具体接口的列表?
【发布时间】:2009-11-20 19:04:02
【问题描述】:

为什么不能编译?

public interface IConcrete { }

public class Concrete : IConcrete { }

public class Runner
{
    public static void Main()
    {
        var myList = new List<Concrete>();
        DoStuffWithInterfaceList(myList);  // compiler doesn't allow this
    }

    public static void DoStuffWithInterfaceList(List<IConcrete> listOfInterfaces) { }

}

myList 设置为正确类型的最快方法是什么?

编辑 我搞砸了 DoStuffWithInterfaceList 示例

【问题讨论】:

  • 有人对此有任何更新吗?十年后还是一样的答案?

标签: c#


【解决方案1】:

对于大型列表,公认的解决方案效率很低,而且完全没有必要。您可以稍微更改方法的签名以使代码在任何隐式或显式转换的情况下工作:

public class Runner
{
    public static void Main()
    {
        var myList = new List<Concrete>();
        DoStuffWithInterfaceList(myList);  // compiler doesn't allow this
    }

    public static void DoStuffWithInterfaceList<T>(List<T> listOfInterfaces)
        where T: IConcrete
    { }
}

请注意,该方法现在是通用的,并使用类型约束来确保只能使用 IConcrete 子类型列表调用它。

【讨论】:

  • (顺便说一句:由于上述解决方案在这种情况下总是有效的,所以在 C# 4 中引入泛型协方差可能会被证明是矫枉过正)
  • 伙计,这现在看起来很明显。谢谢。
【解决方案2】:

几乎所有这些答案都说这将在 C# 4 中得到支持。他们都错了。

明确一点:这不是我们将在 C# 4 中支持的协变示例,因为这样做不是类型安全的。 我们支持类型安全的协变和逆变使用引用类型参数构造的泛型接口和委托。此处的示例使用类类型 List,而不是接口类型。并且接口类型 IList 对于协变或逆变不是类型安全的。

IEnumerable 将是协变的,因为它是一个对协变安全的接口。

【讨论】:

    【解决方案3】:

    目前,这是禁止的,否则会破坏类型安全。 你可以在 DoStuffWithInterfaceList 中做这样的事情:

    public class OtherConcrete : IConcrete { }
    
    public void DoStuffWithInterfaceList(List<IConcrete> listOfInterfaces) 
    {
           listOfInterfaces.Add(new OtherConcrete ());
    }
    

    这会在运行时失败,因为 listOfInterfaces 的类型仅为 Concrete。

    正如其他人所说,这在 C# 4 中是可能的,只要您不更改方法内的列表,但您必须明确告诉编译器。

    要回答有关转换列表的其他问题,如果您使用的是 .Net 3.5,我会使用 Enumerable.Cast 扩展方法。否则,您可以使用 yield 关键字自己编写一个惰性转换方法,这将产生相同的效果。

    编辑:

    正如 Eric Lippert 所说,您应该使用 IEnumerable 以便它在 C# 4 中工作。

    【讨论】:

      【解决方案4】:

      这与协变和逆变有关。 Eric Lippert 今年早些时候写了很多关于它的文章。 (11 个专门针对该主题的博客条目。)第一个是 Covariance and Contravariance in C#, Part One。阅读并在他的博客中搜索其余内容。他详细解释了为什么这种事情很难。

      好消息:C# 4.0 中取消了一些限制。

      【讨论】:

        【解决方案5】:

        IList 不起作用,因为 IList 不是逆变的。它需要是 IEnumerable ,尽管这仅在 4.0 中有效。您也可以只使用带有 lambda 表达式的 ConvertAll,这将在 3.5 中工作

        【讨论】:

          【解决方案6】:

          C# 目前不支持转换这样的泛型类型(如果我理解正确,它将在 C# 4 中得到支持正如 wcoenen 在下面的 cmets 中所说,Eric 在他的回答中也澄清了,使它在 C#4 中工作的唯一方法是使用 IEnumerable&lt;IConcrete&gt;)。现在您需要以某种方式转换您的列表。

          你可以这样调用方法:

          DoStuffWithInterface(myList.ConvertAll<IConcrete>(n => n as IConcrete));
          

          更新
          我意识到你可能不需要在 lambda 中进行强制转换,尽管为了清楚起见我有点喜欢它。所以这也应该有效:

          DoStuffWithInterface(myList.ConvertAll<IConcrete>(n => n));
          

          【讨论】:

          • 我知道,我搞砸了这个例子,我的意思是它都对列表进行操作,请参阅我的编辑。谢谢。
          • 当然 C# 4 也不允许这样做,因为否则 DoStuffWithInterface 将能够将 any IConcrete 实现插入到实际上应该只包含 @987654325 的列表中@ 实例。这就是 Eric 所说的 List&lt;T&gt; 不是“协变”的意思。
          • @wcoenen:我认为(没有用于测试的 VS2010)它会起作用,但前提是 DoStuffWithInterface 的参数更改为类型IList&lt;IConcrete&gt;.
          • 我确实有 VS2010 进行测试。使其工作的唯一方法是将参数类型更改为IEnumerable&lt;IConcrete&gt; 或其他协变类型。 IList协变的。
          • @wcoenen:感谢您的澄清。迫不及待想要得到我的新电脑,这样我就可以安装 VS2010 来测试和验证 C#4 问题。
          【解决方案7】:

          你可以试试

          public void DoStuffWithInterface(IList<IConcrete> concrete) { }
          

          但我认为这只适用于 .NET 4.0。

          如果你想变脏,就去做

          public void DoStuffWithInterface(IList concrete) { }
          

          并检查出来的物体是否是混凝土的。

          【讨论】:

            【解决方案8】:
            foreach (var item in myList)
                DoStuffWithInterface(item);
            

            public void DoStuffWithInterface(IList<IConcrete> concrete) { }
            

            var myNewList = myList.Cast<IConcrete>();
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-01-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-03-07
              • 2021-12-25
              • 1970-01-01
              相关资源
              最近更新 更多