【问题标题】:Why is the compiler choosing the generic method over specific one?为什么编译器选择通用方法而不是特定方法?
【发布时间】:2020-12-17 08:16:59
【问题描述】:

我有这个服务接口。

public interface IService
{
    Task SetAsync(string key, string value, TimeSpan? expiration = null);
    Task SetAsync<T>(string applicationName, string key, T value, TimeSpan? expiration = null);
}

我想这样调用第一个方法。

service.SetAsync("Key", "Value", TimeSpan.FromMinutes(1));

方法调用与第一个方法的约定 100% 匹配。然而编译器通过假设TimeSpan 是我的泛型类型来选择第二种方法。

为什么会这样?

【问题讨论】:

  • 它对后面的参数有默认值,这会让事情变得很糟糕。我会尝试找到一个合适的副本
  • 运行其他方法的简单解决方法是明确指定其参数名称:service.SetAsync(key:"Key", "Value", TimeSpan.FromMinutes(1)); (fiddle)
  • 试试new Service().SetAsync("Key", "Value", new TimeSpan?());。因为TimeSpan.FromMinutes(1)TimeSpan 而不是TimeSpan?
  • @Damien_The_Unbeliever:我希望不需要默认参数来使 first 方法更可取。正在调查...
  • 如果您传递一个非空值,那么它会采用第一种方法,或者将您的方法上的TimeSpan? 替换为TimeSpan,它也将采用第一种方法。

标签: c# .net generics


【解决方案1】:

这遵循 ECMA C# 5 标准第 12.6.4.3 节中“更好的函数成员”的规则:

为了确定更好的函数成员,构造了一个精简的参数列表 A,其中仅包含参数表达式本身,按照它们在原始参数列表中出现的顺序。

每个候选函数成员的参数列表按以下方式构造:

  • 如果函数成员仅适用于扩展形式,则使用扩展形式。
  • 从参数列表中删除没有对应参数的可选参数
  • 参数被重新排序,以便它们出现在参数列表中与相应参数相同的位置。

因此,在这一点上,我们正在有效地比较这两种方法:

Task SetAsync(string key, string value, TimeSpan? expiration);
Task SetAsync<T>(string applicationName, string key, T value);

(在泛型方法版本中,expiration参数没有对应的实参,所以从参数列表中移除。)

参数列表为"Key", "Value", TimeSpan.FromMinutes(1)T推断为TimeSpan

下一步:

给定一个带有一组参数表达式 { E1, E2, ..., EN } 的参数列表 A 和两个适用的函数成员 MP 和 MQ,参数类型为 { P1, P2, ..., PN } 和 { Q1 , Q2, ..., QN }, MP 被定义为比 MQ 更好的函数成员 if

  • 对于每个参数,从 EX 到 QX 的隐式转换并不比从 EX 到 PX 的隐式转换好,并且
  • 对于至少一个参数,从 EX 到 PX 的转换优于从 EX 到 QX 的转换。

对于前两个参数,没有区别 - 因为它们到处都是 string

对于第三个参数,非泛型方法的转换是从TimeSpanTimeSpan?,而泛型方法的转换是从TimeSpanTimeSpan(因为TTimeSpan) .

TimeSpanTimeSpan 的身份转换比 TimeSpanTimeSpan? 的隐式转换“更好”(根据 12.6.4.4 的规则),因此对于该参数,通用方法是“更好”......并且没有非泛型方法“更好”的参数,因此总体而言泛型方法更好。

如果在此阶段没有发现任何方法“更好”(例如,由于可选参数的参数类型为 TimeSpan 而不是 TimeSpan?),那么非泛型方法将被确定为在此阶段之后的抢七规则中“更好”。但是到那时,泛型方法已经被选中了。

【讨论】:

  • 啊哈,遵守规则,现在一切都说得通了。谢谢你澄清这个。如果我说昨晚这并没有让我失去一个小时的睡眠,那我就是在撒谎。尤其是 12.6.4.4 的规则使这很容易混淆。我的直觉告诉我,我必须指定 &lt;TimeSpan&gt; 才能让编译器选择通用方法。但这就是 12.6.4.4 的用武之地。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多