当我尝试将元组作为方法的键传递给
Dictionary
时,我在 C# .NET 8.0 中遇到编译错误。该元组由一个基类和另一个类组成,但是当我尝试使用派生类代替元组中的基类时,代码无法编译。这是我的代码的简化版本:
class A{};
class B : A {};
class X{};
void bar(Dictionary<(A sup, X oth), X> MyDicArg) {
// Do something with Dictionary using that tuple
}
void foo() {
var arg = new Dictionary<(B sup, X oth), X>();
bar(arg); // Compilation error here
}
由于 B 是 A 的子类,所以我认为我可以使用键为 (B, X) 的字典,其中由于隐式向上转换,预计键为 (A, X) 的字典,但似乎并非如此正在发生。
有人可以解释为什么会发生这种类型不匹配以及我如何解决这个问题吗?有没有办法让该方法接受元组键中带有派生类的字典,或者我是否需要调整我的方法?
您要查找的术语是 协变(及其相对的逆变。)不幸的是,C# 中的元组既不是协变也不是逆变,因为它们是
struct
,而不是 interface
,在 C# 中,只有接口支持协变/逆变。
在您的具体示例中,您应该认为该限制很好,因为如果不存在此限制,您将被允许潜在地搬起石头砸自己的脚。
这是因为
foo()
有一个派生程度较高的类的字典,并且您希望它将此字典分发给 bar()
,这会将其视为包含派生程度较低的类,但您正在使用 Dictionary
,而不是 IReadOnlyDictionary
,因此 bar()
可以自由地将派生较少的类添加到字典中,从而对 foo()
造成严重破坏。
您遇到的问题是由于 C# 中缺乏对元组类型的协变/逆变支持而发生的。更详细:
方差允许您使用派生程度较高的类型来代替派生程度较低的类型(协方差),反之亦然(逆变)——不幸的是,元组的行为并非如此。
在您的情况下,B 是从 A 派生的,但 (B, X) 不能隐式转换为 (A, X)。这是因为 C# 中的元组类型不会自动支持协方差。
您可以采取解决方法,将 (B,X) 转换为 (A,X),例如:
void foo()
{
var arg = new Dictionary<(B sup, X oth), X>();
var convertedArg = arg.ToDictionary(entry => ((A)entry.Key.sup, entry.Key.oth), entry => entry.Value);
bar(convertedArg);
}