【问题标题】:Why won't a Func with a nullable return type fit into a Dictionary holding Funcs with object return types?为什么具有可为空返回类型的 Func 不适合包含具有对象返回类型的 Func 的字典?
【发布时间】:2020-02-05 21:58:49
【问题描述】:

我正在尝试构建一个看起来像这样的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}
};

我想放入的各种Funcs都有不同的返回类型。

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 是一个单独的类,它继承自 INotMine,我无法更改它。

用返回byte[]string 的过程初始化Dictionary 的两行代码编译没有问题。 返回可空数据类型的过程的四行(即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)}

对此我很感激,我会使用它,但我仍然想知道为什么原始行无法编译。

【问题讨论】:

  • 它们无法编译,因为 C# 编译器拒绝隐式发出包装委托以将值类型 Nullable&lt;T&gt; 转换为 object,这需要装箱转换(这基本上是解决方法提供)。是的,每种类型名义上都继承自 object,包括值类型,但它们仍然被特殊对待。

标签: c# func unboxing


【解决方案1】:

考虑以下几点:

Func<Animal, Turtle> fat = whatever;
Func<Mammal, Reptile> fmr = fat;

这是合法的。 fat 可以接受任何 Animal,fmr 的调用者承诺传递一个Mammal,它始终是一个Animal。同样fmr 要求函数返回Reptile,而fat 总是返回Turtle,即Reptile

这种转换称为变体转换。具体来说,“我需要任何动物,所以我会接受哺乳动物”被称为 逆变 转换,而“我返回一只乌龟,所以我满足你对爬行动物的需求”是一个协变 转换。

C# 仅在以下情况下支持泛型类型的协变和逆变转换:

  • 有问题的类型是委托或接口。
  • 该类型已被标记为安全变量,并且编译器已确认它是安全的。
  • 变化的类型参数都是引用类型

您满足前两个条件 - 您的 Func&lt;string, object&gt; 是一个委托类型,已知它在参数中是逆变的,在返回时是协变的。但是你不满足第三个条件。 bool?DateTime?double?int?不是引用类型。因此,变体转换规则不适用,并且转换是非法的。

这将包括从Func&lt;string, bool?&gt; 转换为Func&lt;string, object&gt;。但这不是你正在做的;您正在从包含返回bool? 的函数的方法组 转换。这有关系吗?

没有。方法组转换的规则相同。从包含返回bool?Func&lt;string, object&gt; 的方法的方法组的协变转换是不合法的,因为bool?仍然必须是引用类型才能使该转换合法。 p>

无论你怎么切,你都会被卡住。您不能将返回bool? 直接 的方法或委托转换为返回object 的委托。

Jeroen 对该问题的评论很好地总结了其原因。 装箱转换到哪里去?装箱转换是分配堆内存的代码,因此该代码必须去某个地方,但没有去处让编译器放置它并仍然在委托上维护引用标识。

正如您正确指出的,如果您提供自己的 lambda,那么编译器就有放置装箱转换的位置;它将它贴在 lambda 的返回之上。您为此付出的代价是 lambda 现在是一个代表 lambda 主体,而不是直接指向 GetBooleanValue,所以那里有一个很小的间接惩罚。

【讨论】:

  • 为什么编译器和/或运行时“在委托上维护引用标识”是个问题?如果在水下生成并使用包装纸,那么真正重要的东西会破裂吗?在大多数情况下(包括这个特定的情况),这不会是一个问题,因为程序员通常不关心委托的身份(并且您这样做的情况,如事件处理程序,是可区分的)。这是一个“我们不能在所有情况下都透明地完成这项工作,所以不要让他们写出来”的场景吗?
  • @JeroenMostert:我们这里有一个很好的例子,说明了 C# 和 VB 的不同之处。两者都采取有原则的方法,但原则不同! VB 是一种“蒙混过关,按照开发人员的意思去做,以使程序正常工作”的语言; VB 将构建一个包装器。 C# 是一种“按照开发人员所说的去做,如果有问题就说出来”的语言; C# 假设​​如果您正在执行看起来像 引用转换 的操作,那么您希望保持引用标识,并且如果无法完成转换将拒绝进行。
  • 好吧,我们可以通过发明一种全新的“超级重复协变委托转换”来满足 C# 和开发人员的需求,其中包括在必要时构建包装器,然后了解开发人员在做什么以及 C# 的想法他们正在做的事情会匹配。 :-) 我的问题与其说是为什么 C# 拒绝在其转换规则上妥协,不如说是出于实用性的考虑,而是存在或反对转换规则的障碍是更复杂的以一种不混乱的方式涵盖这一点。 (这可能是“负 100 分”的情况,我不知道。)
  • @JeroenMostert:C# 可以设计和实现该规则,当然; VB 做到了。像这样的案例归结为判断电话,您必须平衡在没有数据的情况下评估的大量竞争设计原则。显然,这个决定至少让一个用户感到困惑,因为他们在这里发布了一个问题,但是为少数开发人员消除了这种混淆来源,确实为那些认为他们可以比较引用转换的委托以实现引用相等性和不会对结果感到惊讶吗?很难说。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-15
  • 1970-01-01
  • 2019-07-02
  • 1970-01-01
相关资源
最近更新 更多