为什么List.AsReadOnly返回一个ReadOnlyCollection但Dictionary.AsReadOnly返回一个IReadOnlyDictionary?

时间:2016-07-17 08:08:18

标签: c# .net readonly-collection

我遇到一个案例,其中List.AsReadOnly()返回ReadOnlyCollection而不是IReadOnlyCollection的事实让我很难。由于返回的集合是Value的{​​{1}},因此无法自动向上转发为Dictionary。这看起来很奇怪,在查看.Net源代码后,我确认IReadOnlyCollection方法对AsReadOnly()的处理方式与对List的处理方式不同,即返回具体类而不是界面。

任何人都能解释为什么会这样吗?这种不一致似乎是一种损害,特别是因为我们希望尽可能使用接口,特别是在公开时。

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

IReadOnlyDictionary<T, ReadOnlyCollection<T>>

哦,因为我总是把协方差和矛盾变得困惑,有人可以告诉我哪一个阻止自动演员,并试着以明智的方式提醒我如何记住他们的未来? (例如,_____ [输入/输出]参数的集合不是______variant [co / contra]。)我理解为什么这不可能,因为可能有很多接口的实现,并且不能安全地投射所有字典的各个元素到请求的类型。除非我吹即使这个简单的方面,我理解它,在这种情况下,我希望你可以帮助我正确...

1 个答案:

答案 0 :(得分:0)

原因是历史性的。 {4.5}中添加了IReadOnly *接口,而在.NET 2.0中添加了List<T>.AsReadOnly()。改变其回报类型将是一个重大改变。

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

关于{co,contra}方差,我发现使用C#关键字in(逆变)和out(协变)更容易记住。 in类型参数只能显示为输入方法参数,而out类型参数只能显示为输出(返回值)。一种接受参数的方法,例如使用Base类型调用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

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

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

您可以看到他们希望如何通过通用差异避免混乱。据推测,他们可以添加允许in(cotravariant)方向的只写接口,但我猜他们并没有在那里找到很多价值。