前言:如果您想要完整的解释,请跳到编辑部分。剧透:扩展方法是编译器的技巧,实际上在调用它们时有比它们看起来多一个参数。
问题出在方法组的解析上。显然,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
的一部分。