如果我尝试这样做:
IDictionary<uint, IEnumerable<string>> dict = new Dictionary<uint, List<string>>();
我收到错误:
错误CS0266:无法隐式转换类型
'System.Collections.Generic.Dictionary>'
到
'System.Collections.Generic.IDictionary>'。
存在显式转换(您是否缺少强制转换?)
如果我添加强制转换:
IDictionary<uint, IEnumerable<string>> dict = (IDictionary<uint, IEnumerable<string>>)new Dictionary<uint, List<string>>();
然后它会编译。
为什么我需要显式转换?安全吗?我认为协方差的全部要点是隐式安全转换的能力?
编辑:
C# 防止不相关的转换,例如
string s = (string)0L;
错误CS0030:无法将类型“long”转换为“string”
当您知道该对象实际上是子类时,它确实允许显式向下转换相关类型:
Animal animal = new Cat();
Cat cat = (Cat)animal;
我很困惑为什么编译器提供并允许我显式转换为IDictionary具有不兼容的类型。
If I try and do:
IDictionary<uint, IEnumerable<string>> dict = new Dictionary<uint, List<string>>();
I get the error:
error CS0266: Cannot implicitly convert type
'System.Collections.Generic.Dictionary>'
to
'System.Collections.Generic.IDictionary>'.
An explicit conversion exists (are you missing a cast?)
If I add the cast:
IDictionary<uint, IEnumerable<string>> dict = (IDictionary<uint, IEnumerable<string>>)new Dictionary<uint, List<string>>();
Then it compiles.
Why do I need the explicit cast? And is it safe? I thought the whole point on covariance was the ability to implicitly cast safely?
EDIT:
C# prevents unrelated casting eg
string s = (string)0L;
error CS0030: Cannot convert type 'long' to 'string'
It does allow explicit downcasting of related types when you know that the object is actually a subclass:
Animal animal = new Cat();
Cat cat = (Cat)animal;
I am confused why the compiler is offering, and allowing me to explicitly cast to an IDictionary with incompatible types.
发布评论
评论(3)
IDictionary
对于 TKey 或 TValue 都不是协变的。协变意味着 IDictionary 只能生成 TKey/TValue 类型,但由于它既可以生成也可以使用它们,因此它不能是协变的,也不能是逆变的。
我将用常用术语定义协方差/逆变;
IProducer
是协变的,因此这意味着它仅生成 T 类型。因此,当您将其传递给具有更抽象 T 的 IProducer 的引用时,转换是隐式的,因为以下陈述是正确的:“苹果的生产者是水果的生产者”。 (与“水果生产者不一定是苹果生产者”相反)IConsumer
是逆变的,这意味着仅消耗 T 类型。当您将其传递给对更具体的 T 的引用时,转换是隐式的,因为以下陈述是正确的:“水果的消费者就是苹果的消费者”。 (与“苹果的消费者不一定是任何水果的消费者”相对)这在 IDictionary 的情况下意味着什么,特别是这里的 TValue:
IDictionary 具有生成 TValue 的方法以及消耗 TValue 的方法。话虽如此,这意味着它没有(也不能)被声明为协变或逆变。 (请参阅http://msdn.microsoft.com/en-us/library/s4ys34ea.aspx - 通用接口定义中没有“out”或“in”)
这意味着当您尝试隐式转换您的
Dictionary>
转换成IDictionary>
,编译器会说“等一下,你构建的对象只能接受List< ;string>
在 Add 方法中,但您将其放入允许任何IEnumerable
的引用中,即更大子集,如果您添加IEnumerable
但不是List
的内容,则它将不起作用。 ”它不会(也不能)隐式地允许它,这就是为什么你需要硬转换。(感谢 mquander 提供的具体示例)
IDictionary<TKey, TValue>
is not covariant, either for TKey or TValue.Covariance would mean that IDictionary could solely produce TKey/TValue types, but since it can produce as well as consume them, it cannot be covariant, nor contravariant for that matter.
I'll define covariance / contravariance in common terms;
IProducer<out T>
is covariant, so this means that it only produces T types. Thus when you pass it to a reference to an IProducer with a more abstract T, the cast is implicit, because the following statement is true: "A producer of apples IS a producer of fruit". (to be opposed to "A producer of fruit is not necessarily a producer of apples")IConsumer<in T>
is contravariant, which means in only consumes T types. When you pass it to a reference to a more concrete T, the cast is implicit, because the following statement is true: "A consumer of fruit IS a consumer of apples". (to be opposed to "A consumer of apples is not necessarily a consumer of any fruit")What this means in the case of IDictionary, regarding specifically the TValue here:
IDictionary has methods that produce TValues as well as methods that consume TValues. That being said, it means it wasn't (and couldn't) declared as either covariant or contravariant. (see http://msdn.microsoft.com/en-us/library/s4ys34ea.aspx - there is no "out" or "in" in the generic interface definition)
This means that when you try to implicitely cast your
Dictionary<uint, List<string>>
into anIDictionary<uint, IEnumerable<string>>
, the compiler says "Wait a minute, the object you have built can only acceptList<string>
in an Add method but you're putting it into a reference that will allow anyIEnumerable<string>
in, which is a larger subset. If you Add anything that is anIEnumerable<string>
but isnt aList<string>
, it won't work." It doesn't (and can't) allow it implicitely, which is why you need the hard cast.(thanks to mquander for the specific example)
这是不安全的。例如,您现在可以编写
dict.Add(5, new string[0])
,这会爆炸,因为string[]
不是列表<字符串>
。事实上它不安全,这就是你需要演员的原因。编辑以解决您更新的问题:
C# 允许从任何引用类型 S 到任何接口 T 的任何显式转换(“假设 S 未密封并且假设 S 不实现 T。”)此行为在该语言的第 6.2.4 节中指定规格所以这是合法的:
我不能说为什么会出现这种情况,除了 C# 类型系统最初比现在受到更多限制(例如没有泛型,没有变体)这一事实之外,所以我确信有在很多情况下,能够通过强制转换来破解它是非常方便的。
It is not safe. For example, you could now write
dict.Add(5, new string[0])
, which would blow up, since astring[]
is not aList<string>
. The fact that it is unsafe is why you need the cast.Edit to address your updated concern:
C# allows any explicit cast from any reference type S to any interface T ("provided S is not sealed and provided S does not implement T.") This behavior is specified in section 6.2.4 of the language spec. So this is legal:
I can't say why this is the case, other than the fact that the C# type system was originally even more constrained than it is today (e.g. no generics, no variance) so I'm sure that there were a lot of cases in which being able to hack around it with casts was very convenient.
您可以使用
您正在将代码中的 TValue 类型更改为具体的 List 实现。那是行不通的。您必须使用与声明类型相同的定义。
通过以上内容,您可以将其用作:
等。
You can use
You are changing the TValue type in your code to a concrete List implementation. That will not work. you have to use the same definition as the declaring type.
With the above, you can use it as:
etc.