【问题标题】:Converting an extension method group to a delegate with a generic type将扩展方法组转换为具有泛型类型的委托
【发布时间】:2012-03-24 23:35:29
【问题描述】:

我在 IDataReader 上有两个扩展方法,具有以下签名:

internal static List<T> GetList<T>(this IDataReader reader, Func<string, T> del)

internal static double? GetDoubleOrNull(this IDataReader reader, string columnName)

GetDoubleOrNull 没有任何重载。

在其他地方,我可以做到

Func<string, double?> del = reader.GetDoubleOrNull;

var x = reader.GetList(del);

var x = reader.GetList<double?>(reader.GetDoubleOrNull);

或者只是传入一个实例方法,比如

public double? blah(string s)

var x = reader.GetList(blah);

但我做不到

var x = reader.GetList(reader.GetDoubleOrNull);

编译器报错

cannot convert from 'method group' to 'System.Func<string,double?>'

我不明白这一点。我认为由于GetDoubleOrNull 上没有重载,因此不会有重载决议,它可以从方法签名中推断类型参数。

真正令人困惑的部分是它在传入blah 时似乎是如何工作的。

【问题讨论】:

  • 非常有趣。显式转换 var x = reader.GetList((Func&lt;string, double?&gt;)reader.GetDoubleOrNull) 也可以。 Resharper 将强制转换标记为冗余,但没有它编译会失败。乔恩·斯基特在吗?
  • 有没有办法调用 Jon Skeet(或者 Eric Lippert)?说出他们的名字三遍还是什么?

标签: c# generics delegates extension-methods internals


【解决方案1】:

GetDoubleOrNull 返回一个双精度值? GetList 需要一个 IDataReader 和一个 System.Func&lt;string,int?&gt;

错误信息有点误导

公共双人间?废话(字符串 s)

var x = reader.GetList(blah);

blah 是此次通话的代表。在GetList(GetDoubleOrNull)中是get doubleOrNull的结果。

不过,这是个好问题。无论我是否提供帮助,请务必发布答案。

【讨论】:

  • int 一定是错字,改成double 还是会出现编译错误。
  • 您说“在 GetList(GetDoubleOrNull) 中,它是 get doubleOrNull 的结果”,但这是不对的。在这种情况下,GetDoubleOrNull 没有被评估,它被视为一个方法组。
  • 绝不意味着它正在被评估。与 GetList 的参数相比,GetDoubleOrNull 的签名是什么。
【解决方案2】:

前言:如果您想要完整的解释,请跳至编辑。剧透:扩展方法是一种编译器技巧,实际上比调用它们时看起来要多一个参数

它在方法组的分辨率中。显然 C# 编译器不会花时间来确定您使用的方法是否有重载;它总是需要一个明确的演员表。签出:

What is a method group in C#?
Method Inference does not work with method group

reader.GetDoubleOrNull 返回的方法组会根据您尝试将其转换为的内容缩小范围:GetDoubleOrNull 可以引用任意数量的具有该名称的重载方法。你必须明确地转换它。

有趣的是,出于同样的原因,您甚至不能将方法组分配给隐式类型变量:

var x = reader.GetDoubleOrNull;

编译失败,因为它需要显式转换。

编辑我很确定这里的混淆与扩展方法有关:

查看以下测试类:

public static class Extensions
{
    public static List<T> GetList<T>(this IDataReader reader, Func<string, T> del)
    {
        throw new NotImplementedException();
    }

    public static double? GetDoubleOrNull(this IDataReader reader, string columnName)
    {
        throw new NotImplementedException();
    }

    public static double? blah(this string s)
    {
        throw new NotImplementedException();
    }
}

可以成功调用

var x = reader.GetList(Extensions.blah);

为什么会这样blah 也是一种静态扩展方法,因此,根据您的证据,上述行似乎不应该编译。更复杂的事情,让我们添加这个方法:

public static List<T> GetList2<T>(this IDataReader reader, Func<IDataReader, string, T> del) 
{ 
    throw new NotImplementedException(); 
}

你现在可以打电话了

x = reader.GetList2(Extensions.GetDoubleOrNull);

它会正确编译。什么给了?

答案如下:扩展方法实际上不会向您的对象添加方法。它们确实是一种编译器技巧,可让您像这些方法一样进行编程你的课。来自here

在您的代码中,您使用实例方法调用扩展方法 句法。然而,中间语言(IL)由 编译器将您的代码转换为对静态方法的调用。 因此,封装的原理并不是真正的被 违反。实际上,扩展方法不能访问私有变量 在他们扩展的类型中。

所以,当你打电话时

var x = reader.GetDoubleOrNull("myColumnName");

实际上正在编译和执行的内容本质上是这样的(一个完全合法的调用,即使它是一个扩展方法):

var x = Extensions.GetDoubleOrNull(reader, "myColumnName");

因此,当您尝试使用 GetDoubleOrNull 作为 Func&lt;string, double?&gt; 的 arg 时,编译器会运行“我可以将 GetDoubleOrNull 转换为 Func&lt;IDataReader, string, double?&gt;,因为它有两个参数,但我不知道如何把它变成Func&lt;string, double?&gt;"

即使您将其称为带有一个 arg 的 IDataReader 的实例方法,但它不是:它只是 C# 编译器欺骗的带有两个 args 的静态方法你进入思考是IDataReader的一部分。

【讨论】:

  • 优秀的答案和研究。我想再次 +1。 ;-)
  • 我不是 Jon Skeet,但我会尽我所能:D
  • 感谢您的回答。不幸的是,我似乎仍然没有清楚地理解这个问题。你提到的第一个问题是我知道的。我阅读了第二个,以及 AakashM 发布的那些,在我看来,这归结为重载解决方案如何不考虑返回类型。我明白那个。但是,那么,传递blah 是如何工作的呢?在这种情况下是否没有发生过载解决方案?如果不是,为什么不呢?如果是,它是如何成功的?
  • 不需要解决你告诉它如何处理公共双重?等等(字符串 s);
  • @Moss:我更新了答案以更清晰地解释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-09
  • 1970-01-01
  • 2018-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多