为什么具有可空 return 类型的 Func 不适合包含具有对象 return 类型的 Func 的字典?
Why won't a Func with a nullable return type fit into a Dictionary holding Funcs with object return types?
我正在尝试构建一个如下所示的 Dictionary
:
var getValueFuncs = new Dictionary<TypeID, Func<string, object>>
{
{TypeID.BINARY, NotMine.GetBinaryValue},
{TypeID.BOOLEAN, NotMine.GetBooleanValue},
{TypeID.DATE, NotMine.GetDateTimeValue},
{TypeID.DOUBLE, NotMine.GetFloat64Value},
{TypeID.LONG, NotMine.GetInteger32Value},
{TypeID.STRING, NotMine.GetStringValue}
};
我要放入的各种Func
都有不同的return类型
public interface INotMine : ICollection, ICloneable
{
byte[] GetBinaryValue(string propName);
bool? GetBooleanValue(string propName);
DateTime? GetDateTimeValue(string propName);
double? GetFloat64Value(string propName);
int? GetInteger32Value(string propName);
string GetStringValue(string propName);
}
请注意 NotMine
是一个单独的 class,它继承自 INotMine
,我无法更改它。
用return byte[]
和string
编译的程序来初始化Dictionary
的两行没有问题。 return 可空数据类型 (即 bool?
、DateTime?
、double?
和 int?
过程的四行) 不会编译。他们每个人都报告了这样一个错误:
bool? INotMine.GetBooleanValue(string propName)
'bool? INotMine.GetBooleanValue(string)' has the wrong return type
Expected a method with 'object GetBooleanValue(string)' signature
鉴于所有内容都继承自 object
,为什么这些行不能编译并很好地进入我的 Dictionary
?
编辑:
在他删除它之前,有人发布了一个答案,其中包含一个优雅的解决方法,如下所示:
{TypeID.BINARY, s => NotMine.GetBinaryValue(s)}
非常感谢,我会使用它,但我仍然想知道为什么原始行无法编译。
考虑以下几点:
Func<Animal, Turtle> fat = whatever;
Func<Mammal, Reptile> fmr = fat;
这是合法的。 fat
可以接受任何 Animal,fmr
的调用者承诺传递一个 Mammal
,它始终是一个 Animal
。同样,fmr
要求函数 return 是 Reptile
,而 fat
总是 return 是 Turtle
,即 Reptile
。
这种转换称为 变体 转换。具体来说,"I require any Animal so I'll accept Mammal"称为逆变转换,"I return a Turtle so I meet your need for a Reptile"称为协变转换。
C# 仅在这些情况下支持泛型类型的协变和逆变转换:
- 有问题的类型是委托或接口。
- 该类型已被标记为安全的变化,编译器已确认它是安全的。
- 变化的类型参数都是引用类型.
您满足前两个条件 -- 您的 Func<string, object>
是一个已知的委托类型,在参数中是逆变的,在 return 中是协变的。但是你不满足第三个条件。 bool?
DateTime?
、double?
和 int?
不是引用类型。因此,变量转换规则不适用,转换是非法的。
这将涵盖从 Func<string, bool?>
到 Func<string, object>
的转换。但这不是你在做的;您正在从 方法组 转换而来,其中包含一个 returns bool?
的函数。这重要吗?
没有。方法组转换的规则相同。从包含 returns bool?
的方法的方法组到 Func<string, object>
的协变转换是不合法的,因为 bool?
仍然是 必须是引用类型才能使该转换合法。
无论你怎么切,你都会被卡住。您不能将 returns bool?
的方法或委托直接 转换为 returns object
的委托。
Jeroen 对该问题的评论很好地总结了其原因。 装箱转换去哪里?装箱转换是分配堆内存的代码,因此该代码必须某处,但是编译器没有地方可以放置它并且仍然在委托上保持引用标识。
正如您正确指出的那样,如果您提供自己的 lambda,那么编译器就有地方进行装箱转换;它将它粘在 lambda 的 return 之上。您为此付出的代价是 lambda 现在是一个引用 lambda 主体 的委托,而不是直接指向 GetBooleanValue
,因此那里有一个很小的间接惩罚。
我正在尝试构建一个如下所示的 Dictionary
:
var getValueFuncs = new Dictionary<TypeID, Func<string, object>>
{
{TypeID.BINARY, NotMine.GetBinaryValue},
{TypeID.BOOLEAN, NotMine.GetBooleanValue},
{TypeID.DATE, NotMine.GetDateTimeValue},
{TypeID.DOUBLE, NotMine.GetFloat64Value},
{TypeID.LONG, NotMine.GetInteger32Value},
{TypeID.STRING, NotMine.GetStringValue}
};
我要放入的各种Func
都有不同的return类型
public interface INotMine : ICollection, ICloneable
{
byte[] GetBinaryValue(string propName);
bool? GetBooleanValue(string propName);
DateTime? GetDateTimeValue(string propName);
double? GetFloat64Value(string propName);
int? GetInteger32Value(string propName);
string GetStringValue(string propName);
}
请注意 NotMine
是一个单独的 class,它继承自 INotMine
,我无法更改它。
用return byte[]
和string
编译的程序来初始化Dictionary
的两行没有问题。 return 可空数据类型 (即 bool?
、DateTime?
、double?
和 int?
过程的四行) 不会编译。他们每个人都报告了这样一个错误:
bool? INotMine.GetBooleanValue(string propName)
'bool? INotMine.GetBooleanValue(string)' has the wrong return type
Expected a method with 'object GetBooleanValue(string)' signature
鉴于所有内容都继承自 object
,为什么这些行不能编译并很好地进入我的 Dictionary
?
编辑: 在他删除它之前,有人发布了一个答案,其中包含一个优雅的解决方法,如下所示:
{TypeID.BINARY, s => NotMine.GetBinaryValue(s)}
非常感谢,我会使用它,但我仍然想知道为什么原始行无法编译。
考虑以下几点:
Func<Animal, Turtle> fat = whatever;
Func<Mammal, Reptile> fmr = fat;
这是合法的。 fat
可以接受任何 Animal,fmr
的调用者承诺传递一个 Mammal
,它始终是一个 Animal
。同样,fmr
要求函数 return 是 Reptile
,而 fat
总是 return 是 Turtle
,即 Reptile
。
这种转换称为 变体 转换。具体来说,"I require any Animal so I'll accept Mammal"称为逆变转换,"I return a Turtle so I meet your need for a Reptile"称为协变转换。
C# 仅在这些情况下支持泛型类型的协变和逆变转换:
- 有问题的类型是委托或接口。
- 该类型已被标记为安全的变化,编译器已确认它是安全的。
- 变化的类型参数都是引用类型.
您满足前两个条件 -- 您的 Func<string, object>
是一个已知的委托类型,在参数中是逆变的,在 return 中是协变的。但是你不满足第三个条件。 bool?
DateTime?
、double?
和 int?
不是引用类型。因此,变量转换规则不适用,转换是非法的。
这将涵盖从 Func<string, bool?>
到 Func<string, object>
的转换。但这不是你在做的;您正在从 方法组 转换而来,其中包含一个 returns bool?
的函数。这重要吗?
没有。方法组转换的规则相同。从包含 returns bool?
的方法的方法组到 Func<string, object>
的协变转换是不合法的,因为 bool?
仍然是 必须是引用类型才能使该转换合法。
无论你怎么切,你都会被卡住。您不能将 returns bool?
的方法或委托直接 转换为 returns object
的委托。
Jeroen 对该问题的评论很好地总结了其原因。 装箱转换去哪里?装箱转换是分配堆内存的代码,因此该代码必须某处,但是编译器没有地方可以放置它并且仍然在委托上保持引用标识。
正如您正确指出的那样,如果您提供自己的 lambda,那么编译器就有地方进行装箱转换;它将它粘在 lambda 的 return 之上。您为此付出的代价是 lambda 现在是一个引用 lambda 主体 的委托,而不是直接指向 GetBooleanValue
,因此那里有一个很小的间接惩罚。