为什么 List.AsReadOnly return 是 ReadOnlyCollection 而 Dictionary.AsReadOnly return 是 IReadOnlyDictionary?

Why does List.AsReadOnly return a ReadOnlyCollection but Dictionary.AsReadOnly returns an IReadOnlyDictionary?

我 运行 遇到一个案例,其中 List.AsReadOnly() returns 一个 ReadOnlyCollection 而不是 IReadOnlyCollection 的事实让我很难过。由于返回的集合是 DictionaryValue,因此无法自动向上转换为 IReadOnlyCollection。这似乎是 st运行ge,在查看 .Net 源代码后,我确认 AsReadOnly() 方法对 List 的作用与对 Dictionary 的作用不同,即,返回具体 class 而不是接口。

谁能解释这是为什么?这种不一致似乎是一种伤害,尤其是因为我们希望尽可能使用接口,尤其是在 public.

在我的代码中,起初我在想,既然我的消费者只是一个私有方法,我可以将它的参数签名从 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
)

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

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

显式转换还不错。它甚至不是运行时强制转换,因为编译器可以静态验证它(没有向 IL 发出强制转换)。顺便说一下,您可以将其转换为 IReadOnlyList<T>,它也提供对列表的索引访问。您还可以编写一个 return 是您需要的类型的扩展方法(例如 AsReadOnlyList())。

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

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

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

你可以看到他们是如何避免泛型方差的混乱。据推测,他们可以添加允许 in(协变)方向的只写接口,但我猜他们没有在其中找到很多价值。