假设我有一个功能
DoSomething(Dictionary<object, object> dict)
为什么不能用
调用它 DoSomething(new Dictionary<int, string>())
?
由于int
和string
都继承自object
,我希望这会有效。我知道我可以将其扩展到
DoSomething<T1, T2>(Dictionary<T1, T2> dict)
,但为什么第一个选项不起作用?
答案 0 :(得分:4)
这是因为Dictionary<int, string>
不是Dictionary<object, object>
。
要实现这一点,类Dictionary<TKey, TValue>
需要在TKey
和TValue
中协变,并且协方差需要使用值类型(装箱)。< / 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)
仅仅因为B
是A
的子类型,并不意味着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();
由于您在构建期间不会收到警告,因此最终会出现间歇性错误。