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

13

我在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 时它似乎是如何工作的。


3
很有趣。显式转换“var x = reader.GetList((Func<string, double?>)reader.GetDoubleOrNull)”也是有效的。Resharper将此转换标记为冗余,但如果没有它,编译将失败。Jon Skeet在周围吗? - Jacek Gorgoń
3
有没有一种方法可以召唤Jon Skeet(或者是Eric Lippert)?比如说,重复他们的名字三遍什么的? - Chris
1
请参考以下两个链接:https://dev59.com/9msz5IYBdhLWcg3wpZry 和 https://dev59.com/AHI95IYBdhLWcg3w_zas。 - AakashM
2个回答

8
前言:如果您想要完整的解释,请跳到编辑部分。剧透:扩展方法是编译器的技巧,实际上在调用它们时有比它们看起来多一个参数。
问题出在方法组的解析上。显然,C#编译器不会花时间去判断你使用的方法是否有重载;它总是需要一个显式转换。请看: 什么是C#中的方法组? 方法推断无法与方法组一起使用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);

它将会正确编译。是什么原因呢?

答案在这里:扩展方法并没有实际地向对象添加方法。它们实际上只是编译器的技巧,使您可以像使用类中的方法一样编程。来自这里

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

所以,当您调用

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

实际上编译并执行的是这个(即使它是扩展方法,也是完全合法的调用):
var x = Extensions.GetDoubleOrNull(reader, "myColumnName");

因此,当您尝试将GetDoubleOrNull用作Func<string, double?>的参数时,编译器会认为“我可以将GetDoubleOrNull转换为Func<IDataReader, string, double?>,因为它有两个参数,但我不知道如何将其转换为Func<string, double?>”。

尽管您将其视为IDataReader实例方法并使用一个参数进行调用,但实际上不是这样的:它只是一个带有两个参数的静态方法,C#编译器让您误以为它是IDataReader的一部分。


非常好的回答和研究。我想再点赞一次。;-) - Chris
3
我不是Jon Skeet,但我会尽力而为:D - eouw0o83hf
现在清楚多了,谢谢。不过我们再往前推进一步。虽然我可以执行 x = reader.GetList2(Extensions.GetDoubleOrNull);,但我仍然无法执行 var x = reader.GetList2(reader.GetDoubleOrNull);。会出现“无法从用法中推断类型参数”错误。 - Moss
这很奇怪:var x = reader.GetList<double?>(reader.GetDoubleOrNull); 也可以工作。:S - Moss
我认为这是版本4编译器的一个错误。在版本5中,这个程序可以顺利编译。(当然,在提出问题时我们并不知道这一点) - Andrew Savinykh
显示剩余3条评论

0

GetDoubleOrNull 返回一个 double 类型的值吗? GetList 需要一个 IDataReader 和一个 System.Func<string,int?>

错误信息有点误导人

public double? blah(string s)

var x = reader.GetList(blah);

在这个调用中,blah 是一个委托。在 GetList(GetDoubleOrNull) 中,它是 get doubleOrNull 的结果。

不错的问题。无论我是否有帮助,请发布答案。


“int” 必须是一个打字错误,将其更改为 “double” 仍然会导致编译器错误。 - Jacek Gorgoń
1
你说“在 GetList(GetDoubleOrNull) 中,它是 get doubleOrNull 的结果”,但这是不正确的。在这种情况下,GetDoubleOrNull 并没有被评估,而是被视为一个方法组。 - Chris
从来没有意味着正在进行评估。GetDoubleOrNull的签名与GetList的参数相比如何? - Tony Hopkinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接