IEnumerable<T> 转换

5

以下是给定的内容:


class Base<T> {/*...*/}
class Der<T>: Base<T> {/*...*/}

interface Sth<T>{
  IEnumerable<Base<T>> Foo {get;}
}

// and implementation...
class Impl<T>: Sth<T> {
  public IEnumerable<Base<T>> Foo {
    get {
      return new List<Der<T>>();
    }
  }
}

我应该如何让这段代码编译通过?显然,出现了一个错误:List<Der<T>>无法隐式转换为List<Base<T>>。如果我明确将其转换,就会发生InvalidCastException。


5
如果您对这个话题感兴趣,您可能想阅读我在C# 4.0中设计此功能的大型系列文章:http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx。 - Eric Lippert
5个回答

6
您正在尝试进行的转换在 .NET Framework 3.5 版本中不受支持。由于添加了 泛型协变性/逆变性,它将在 4.0 版本(Visual Studio 2010)中得到支持。虽然这仍然不允许您将 List<Der> 强制转换为 List<Base>,但它确实允许您将列表实现的 IEnumerator<Der> 强制转换为 IEnumerator<Base>
同时,您可以编写自己的类来实现 IEnumerable<Base> 并返回一个自定义的 IEnumerator<Base>,它只需包装 List<Der>IEnumerator<Der>。或者,如果您使用的是 .NET Framework 3.5,则可以使用其他人建议的 Cast 扩展方法

1
请注意,通用变异不允许将List<Derived>强制转换为List<Base>,因为List<T>具有类型为T的in和out成员。 (对于在IEnumerable<Base>的位置上使用IEnumerable<Derived>,它将起作用。) - itowlson
如果我理解正确,那么“True, but”将允许OP的代码示例编译。List<Der>实现了IEnumerable<Der>,可以转换为IEnumerable<Base> - bcat
当然,我相信是这样的;我只是提到了这一点,因为在问题文本中他提到了列表类型之间的隐式转换,所以我想澄清你的评论适用于他的代码(使用IEnumerable),而不是他的补充评论(指List)。 - itowlson
那是一个很好的观点。我更新了我的答案,希望能够更清楚地解释一些事情。 - bcat
谢谢解释。我刚刚习惯了C ++,在那里许多事情都可以正常工作:]] - SOReader

4

List<Der<T>>无法转换为List<Base<T>>,因为后者可以添加Base<T>,而前者不行。

您可以使用Cast扩展方法来解决此问题:return new List<Der<T>>().Cast<Base<T>>();


1

让它编译起来...

class Impl<T> : Sth<T>
{
    public IEnumerable<Base<T>> Foo
    {
        get
        {
            return new List<Base<T>>(); //change the List type to Base here
        }
    }
} 

你也可以像这样做,从Der的实现中返回Base类的IEnumerable。
class Impl<T> : Sth<T>
{
    public IEnumerable<Base<T>> Foo
    {
        get
        {
            List<Der<T>> x = new List<Der<T>>();

            foreach (Der<T> dt in x)
            {
                yield return dt;
            }
        }
    }
} 

1

您可以使用LINQ将List<Der<T>>转换为IEnumerable<Base<T>>,方法如下:

class Impl<T>: Sth<T>
{
    public IEnumerable<Base<T>> Foo
    {
        get
        {
            return new List<Der<T>>().Cast<Base<T>>();
        }
    }
}

正如其他答案所述,v3.5 不支持通用协变,但您可以使用 LINQ 创建一个包装对象来实现 IEnumerable<Base<T>>


谢谢,那很有帮助。转换会对应用程序造成多大的减速?它对性能有很大影响吗? - SOReader
拿出一个秒表,试着运行几百万次,然后你就会知道了。你是唯一能够访问你的应用程序计时数据的人;我们无法回答那个问题。 - Eric Lippert
1
我从未特别检查过开销,但由于你是将其转换为基本类型(而不是实际将对象转换为不同类型或装箱值类型),所以我认为这不是昂贵的代码。但Eric是正确的;我们无法确定您的整个应用程序的反应方式;如果您将具有1000亿项的列表进行转换,那么转换所有这些项需要一段时间 :-D - FacticiusVir
此外,在性能方面,请记住LINQ是惰性求值的;这意味着当您调用Cast时,不会执行转换操作,而是在迭代返回的IEnumerable时执行。这可能发生在foreach中(在这种情况下,每次通过foreach循环时都会单独转换项目,从而分散“负载”),或者当您使用ToArray或ToList将IEnumerable转换为更具体的类型时。这也意味着,如果您两次迭代IEnumerable(例如两个单独的foreach循环),则会执行整个转换操作两次。这只是需要注意的一点。 - FacticiusVir

0

这些答案帮助了我,我想分享一下我解决类似问题的方法。

我编写了一个扩展方法,用于自动转换TSource,每当我想将List<Foo>转换为IEnumerable<Bar>时(我仍在3.5上)。

public static SpecStatus Evaluate<TSource, TSpecSource>(this IEnumerable<TSource> source, ISpec<IEnumerable<TSpecSource>> spec)
        where TSource : TSpecSource
{
    return spec.Evaluate(source.Cast<TSpecSource>());
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接