将扩展方法组转换为具有泛型类型的委托

Mos*_*oss 13 c# generics extension-methods delegates internals

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

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

internal static double? GetDoubleOrNull(this IDataReader reader, string columnName)
Run Code Online (Sandbox Code Playgroud)

GetDoubleOrNull 没有任何重载.

在其他地方,我能做到

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

var x = reader.GetList(del);
Run Code Online (Sandbox Code Playgroud)

要么

var x = reader.GetList<double?>(reader.GetDoubleOrNull);
Run Code Online (Sandbox Code Playgroud)

或者只是传入一个类似的实例方法

public double? blah(string s)

var x = reader.GetList(blah);
Run Code Online (Sandbox Code Playgroud)

但我不能这样做

var x = reader.GetList(reader.GetDoubleOrNull);
Run Code Online (Sandbox Code Playgroud)

编译器给出错误

cannot convert from 'method group' to 'System.Func<string,double?>'
Run Code Online (Sandbox Code Playgroud)

我不明白这一点.我认为既然没有重载GetDoubleOrNull,就不会有重载决策,它可以从方法签名推断出类型参数.

真正令人困惑的部分是它在传入时的工作方式blah.

eou*_*3hf 8

前言:如果需要完整说明,请跳至编辑.Spoiler:扩展方法是一种编译器技巧,实际上还有一个参数比它们调用它们时的样子.

它在方法组的分辨率中.显然C#编译器没有花时间弄清楚你使用的方法是否有重载; 它总是需要一个明确的演员.查看:

C#中的方法组是什么?
方法推断不适用于方法组

返回的方法组reader.GetDoubleOrNull通过您尝试将其强制转换为缩小范围:GetDoubleOrNull可以引用具有该名称的任意数量的重载方法.你必须明确地施展它.

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

var x = reader.GetDoubleOrNull;
Run Code Online (Sandbox Code Playgroud)

无法编译,因为它需要显式转换.

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

查看以下测试类:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以成功打电话

var x = reader.GetList(Extensions.blah);
Run Code Online (Sandbox Code Playgroud)

为什么会这样blah也是一种静态扩展方法,因此,根据您的证据,看起来上面的行不应该编译.更复杂的是,让我们添加这个方法:

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

你现在可以打电话了

x = reader.GetList2(Extensions.GetDoubleOrNull);
Run Code Online (Sandbox Code Playgroud)

它会正确编译.是什么赋予了?

答案如下:扩展方法实际上并没有向对象添加方法.它们真的是一个编译器技巧,允许你编程,好像这些方法是你的类的一部分.从这里:

在您的代码中,您使用实例方法语法调用扩展方法.但是,编译器生成的中间语言(IL)会将您的代码转换为静态方法的调用.因此,封装原则并未真正被违反.实际上,扩展方法无法访问它们正在扩展的类型中的私有变量.

所以,当你打电话的时候

var x = reader.GetDoubleOrNull("myColumnName");
Run Code Online (Sandbox Code Playgroud)

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

var x = Extensions.GetDoubleOrNull(reader, "myColumnName");
Run Code Online (Sandbox Code Playgroud)

因此,当你尝试使用GetDoubleOrNullarg作为a时Func<string, double?>,编译器会"我可以GetDoubleOrNull变成a,Func<IDataReader, string, double?>因为它有两个参数,但我不知道如何把它变成一个Func<string, double?>"

即使你把它称为IDataReader带有一个arg 的实例方法,它也不是:它只是一个带有两个args的静态方法,C#编译器欺骗你思考是其中的一部分IDataReader.

  • 我不是Jon Skeet,但我尽我所能:D (3认同)

Ton*_*son 0

GetDoubleOrNull 返回双精度值?GetList 需要一个 IDataReader 和一个System.Func<string,int?>

错误消息有点误导

公共双?等等(字符串 s)

var x = reader.GetList(blah);

blah 是这次电话会议的代表。在 GetList(GetDoubleOrNull) 中,它是 get doubleOrNull 的结果。

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