2014-04-23 67 views
2

为什么?当然,我不需要用户定义的转换,因为List(T)IList(T)HashSet(T)IEnumerable(T)。谢谢。将嵌套集合转换为接口

不能将类型'System.Collections.Generic.List<System.Collections.Generic.HashSet<string>>'隐式转换为'System.Collections.Generic.IList<System.Collections.Generic.IEnumerable<string>>'。一个显式转换存在(是否缺少强制转换?)

class Program { 

    static IList<IEnumerable<string>> GetSet() { 
     return new List<HashSet<string>>(); 
    } 

} 
+8

因为'IList'是不变的(或者更具体地说,它不是协)在其类型参数。如果该方法的声明返回类型是,例如,“IEnumerable >'',您可以返回'List >'。 –

+2

换句话说:问题是关于什么可以传递到列表的方法有冲突的规则。 'IList >'声明你可以用任何'IEnumerable '调用'Add',但是'List >'说它必须是一个HashSet。但是,您可以创建一个'List >',并将HashSets添加到它。 – nmclean

回答

1

因为IList<T>是不变的。

为了说明为什么这是一个问题,请看下面的例子:

IList<T>提供了实例Add(T object)IEnumerable<string>的方法,这将与你的构造表达new List<HashSet<string>>()冲突。这意味着我可以打电话给你的program.GetSet()并添加一个new ArrayList<string>(),但是你构建的实例不允许它,因为它有一个合约,它只包含HashSet<string>实例(当你询问包含ArrayList<T>GetSet()的内容时你会返回什么?

的typeparameter是双重嵌套没关系。比如IList<Object>不是IList<FooClass>的超任的事实。


这不是IEnumerable<T>的情况下, self(意思是IEnumerable<T>也是IEnumerable<SuperT>),因为IEnumerable<T>的唯一功能是输出值。根据Liskov替代原则允许使用。

原理说,在一个类层次走下来的时候,在 返回类型只能成为更普遍的(超类/接口) 和参数类型只能变得更加具体(子 类/接口) 。


C#提供了工具来处理这个原理(所谓方差)通过在泛型类型声明使用inout关键字。

例如,如果你确信一个Foo<SubT>Foo<T>还有,你可以这样定义:

public class Foo<out T> { 

    T getResult() { 
     //do something 
    } 

} 

在这种情况下T是协变对于Foo。如果类型参数仅用作输入,则可以进一步指定。例如Bar<T>显然是Bar<SubT>下面定义的特殊情况:

public class Bar<in T> { 

    void setParameter(T parameter) { 
     //do something 
    } 

} 
2

我想我真的需要是变化的一个简单的解释 - 逆变和协方差 - 在C#泛型的情况下,我已经找到herehere

错误消息并没有真正使我说,但现在我会总结:

逆变

通用类,当然是一个类的模板而不是类定义,可以使用逆向使用关键字。甲逆变类允许从基类的实例分配到一个派生类实例即边境牧羊犬=犬

public interface AllowAssignmentsFromBaseToDerived<in T> 

协方差

泛型类可以使用关键字进行协变。甲协变类允许从派生类实例分配到基类的实例,即狗=边境牧羊犬

public interface AllowAssignmentsFromDerivedToBase<out T> 

方差被支承用于数组类型,因为C#1.0和委托类型,因为C#2.0,以及因为C#通用类型参数4.0。

很高兴能有更多的回复来覆盖我已经错过的更多观点,仍然感觉有点不知情。

更多信息从here解禁:

如何创建变型通用接口和委托自己?

out关键字将类型参数标记为协变,并且 关键字将其标记为逆变。到 两个最重要的规则要记住:

  1. 您可以标记一个泛型类型参数的协变如果只是 作为方法的返回类型,而不是作为一种形式化方法 参数。

  2. 反之亦然,你可以,如果它 只是用来作为一种形式化方法的参数,而不是用作 方法的返回类型标记类型逆变。

interface IVariant<out R, in A> 
    { 
     // These methods satisfy the rules. 
     R GetR(); 
     void SetA(A sampleArg); 
     R GetRSetA(A sampleArg); 

     // And these don’t. 
     // A GetA(); 
     // void SetR(R sampleArg); 
     // A GetASetR(R sampleArg); 
    } 

此外,如果扩展一个变种通用接口是默认不变。您需要根据需要指定In或Out。

最后,我的解释将是远远不够的尝试Eric Lippert's blog