泛型协变和显式转换

发布于 2024-12-19 22:58:49 字数 936 浏览 2 评论 0 原文

如果我尝试这样做:

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

阳光下慵懒的猫 2024-12-26 22:58:49

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 an IDictionary<uint, IEnumerable<string>>, the compiler says "Wait a minute, the object you have built can only accept List<string> in an Add method but you're putting it into a reference that will allow any IEnumerable<string> in, which is a larger subset. If you Add anything that is an IEnumerable<string> but isnt a List<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)

傲鸠 2024-12-26 22:58:49

这是不安全的。例如,您现在可以编写 dict.Add(5, new string[0]),这会爆炸,因为 string[] 不是 列表<字符串>。事实上它不安全,这就是你需要演员的原因。

编辑以解决您更新的问题:

C# 允许从任何引用类型 S 到任何接口 T 的任何显式转换(“假设 S 未密封并且假设 S 不实现 T。”)此行为在该语言的第 6.2.4 节中指定规格所以这是合法的:

var foo = (IList<ICollection<IEnumerable<IntPtr>>>)new Uri(@"http://zombo.com");

我不能说为什么会出现这种情况,除了 C# 类型系统最初比现在受到更多限制(例如没有泛型,没有变体)这一事实之外,所以我确信有在很多情况下,能够通过强制转换来破解它是非常方便的。

It is not safe. For example, you could now write dict.Add(5, new string[0]), which would blow up, since a string[] is not a List<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:

var foo = (IList<ICollection<IEnumerable<IntPtr>>>)new Uri(@"http://zombo.com");

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.

戏剧牡丹亭 2024-12-26 22:58:49

您可以使用

     IDictionary<uint, IEnumerable<string>> dict = new Dictionary<uint, IEnumerable<string>>();

您正在将代码中的 TValue 类型更改为具体的 List 实现。那是行不通的。您必须使用与声明类型相同的定义。

通过以上内容,您可以将其用作:

    dict.Add(1, new List<string>());

等。

You can use

     IDictionary<uint, IEnumerable<string>> dict = new Dictionary<uint, IEnumerable<string>>();

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:

    dict.Add(1, new List<string>());

etc.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文