“通用”类型参数中的类型参数/继承

时间:2013-12-16 15:39:11

标签: c#

假设我有一个功能

DoSomething(Dictionary<object, object> dict)

为什么不能用

调用它

DoSomething(new Dictionary<int, string>())

由于intstring都继承自object,我希望这会有效。我知道我可以将其扩展到

DoSomething<T1, T2>(Dictionary<T1, T2> dict)

,但为什么第一个选项不起作用?

5 个答案:

答案 0 :(得分:4)

这是因为Dictionary<int, string>不是Dictionary<object, object>

要实现这一点,类Dictionary<TKey, TValue>需要在TKeyTValue中协变,并且协方差需要使用值类型(装箱)。< / p>

首先,在.NET中,泛型类型始终是不变的。只有接口类型和委托类型可以是共变或逆变。

其次,Dictionary<,>不是语义协变。例如,您可以说:

myDict.Add(new Elephant(), new BankCustomer());

如果myDict实际上是Dictionary<int, string>变量中的(运行时)Dictionary<object, object>,那就不会很愉快。

现在,从.NET 4.5开始,一些“非字典”类型实现了像IReadOnlyList<out T>这样的协变接口。您可能希望类似的接口IReadOnlyDictionary<TKey, TValue>也是协变的。但事实并非如此(下面的Servy首次发表评论的原因)。所以对你没有希望。

答案 1 :(得分:2)

仅仅因为BA的子类型,并不意味着List<B>List<A>的子类型(或字典,无关紧要)。

如果是,这在编译时是合法的,但会导致运行时异常:

List<A> aList = new List<B>();
aList.Add(new C());

其中C是A的子类型。

要使X<B>成为X<A>的子类型,则X<T>必须在其类型参数T中具有协变性。 要使X具有协变性,T必须仅使用 作为其方法的返回类型,但绝不作为参数。

IEnumerable<T>为例 - 这是一个协变接口。 IEnumerable<string>是[{1}}的子类型,因为IEnumerable<object>仅用作T返回类型。所以在编译和运行时都是合法的:

GetEnumerator

更多关于C#中的协方差和逆变:http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

答案 2 :(得分:2)

因为Dictionary对于两个泛型参数的类型都是不变的。

在C#中,所有通用参数都是不变的。通用参数只能是接口的协变或逆变。

IDictionary接口对于任一类型参数也不能是协变的或逆变的。密钥通过Keys属性传递出来,除了传入...全部,除了被传递出来之外,还使用Add传递值...全部。因为这两种类型都用作输入和输出,所以类型必须不变。

那,协方差/逆变不适用于价值类型;它只能用于引用类型。

只是提供一个简单的例子,如果您被允许进行此类演员,那么我就可以在dict.Add("not an int", new Foo())上致电Dictionary<object, object>。我现在添加了一个不是int的键和一个不是字符串的值。因为允许使用强制转换将允许使用与签名不匹配的类型,所以它不起作用。

答案 3 :(得分:1)

您可以使用它,因为类型不是这样的协变或逆变。举个例子:

var dict=new Dictionary<int,string>();
DoSomething(dict);

DoSomething(Dictionary<object, object> dict)
{
  var now=DateTime.Now;
  dict[now]=now;
}

在您的方案中,您现在已将DateTime添加到[int,string]的字典中!

答案 4 :(得分:0)

仅仅因为Dictionary<T,Q>不是协变的。

如果可能,则以下行将抛出运行时异常;

Dictionary<object, object> dict = new Dictionary<string, string>();
dict["hello"] = new SomeOtherObject();

由于您在构建期间不会收到警告,因此最终会出现间歇性错误。