1

我遇到了一个案例,List.AsReadOnly()返回 aReadOnlyCollection而不是a 的事实IReadOnlyCollection对我来说很困难。由于返回的集合是Valuea 的Dictionary,它不能自动向上转换为 a IReadOnlyCollection。这看起来很奇怪,在查看 .Net 源代码后,我确认该AsReadOnly()方法的作用List与它的作用不同Dictionary,即返回具体类而不是接口。

谁能解释这是为什么?出现这种不一致似乎是一种伤害,特别是因为我们希望尽可能使用接口,尤其是在公共时。

在我的代码中,我起初在想,由于我的消费者只是一个私有方法,我可以将其参数签名从 更改IReadOnlyDictionary<T, IReadOnlyCollection<T>>IReadOnlyDictionary<T, ReadOnlyCollection<T>>。但是,后来我意识到这使得私有方法看起来可能会修改集合值,所以我在前面的代码中添加了一个烦人的显式转换,以便正确使用接口:

.ToDictionary(
   item => item,
   item => (IReadOnlyCollection<T>) relatedItemsSelector(item)
      .ToList()
      .AsReadOnly() // Didn't expect to need the direct cast
)

哦,由于我总是混淆协变和逆变,有人可以告诉我哪个阻止了自动转换,并尝试以一种明智的方式提醒我如何在未来记住它们?(例如,对于_____ [输入/输出]参数,集合不是____variant [co/contra]。)我理解为什么不能这样,因为接口可以有很多实现,并且将所有的字典的各个元素到请求的类型。除非我连这个简单的方面都吹了而且我明白,在这种情况下,我希望你能帮助我纠正...

4

1 回答 1

0

原因是历史的。IReadOnly* 接口是在 .NET 4.5 中添加的,而在List<T>.AsReadOnly().NET 2.0 中又添加回来了。更改其返回类型将是一项重大更改。

明确的演员阵容还不错。它甚至不是运行时强制转换,因为编译器可以静态验证它(不会向 IL 发出强制转换)。顺便说一句,您可以将其转换IReadOnlyList<T>为还提供对列表的索引访问。您还可以编写一个扩展方法来返回您需要的类型(例如AsReadOnlyList())。

in关于 {co,contra}variance,我发现使用 C# 关键字(contravariant) 和out(covariant)更容易记住。in类型参数只能作为输入方法参数出现,而out类型参数只能作为输出(返回值)出现。接受参数(例如 type Base)的方法可以安全地使用 type 调用Derived,因此in在该方向上转换参数是安全的。out恰恰相反。

例如:

interface IIn<in T> { Set(T value); }
IIn<Base> b = ...
IIn<Derived> d = b;
d.Set(derived); // safe since any method accepting Base can handle Derived

interface IOut<out T> { T Get(); }
IOut<Derived> d = ...
IOut<Base> b = d;
b.Get(); // safe since any Derived is Base

非只读集合接口不能是 *-variant,因为它们必须同时是inand out,这将是不安全的。编译器和 CLR 不允许这样做。.NET 确实对数组有一种不安全的变异形式:

var a = new[] { "s" };
var o = (object[])a;
o[0] = 1; // ArrayTypeMismatchException

您可以看到他们希望如何避免这种与通用差异的混乱。大概他们可以添加允许in(协变)方向的只写接口,但我猜他们并没有从中找到很多价值。

于 2016-07-17T08:48:16.523 回答