委托作为扩展方法的第一个参数

本文关键字:第一个 参数 方法 扩展 | 更新日期: 2023-09-27 17:57:58

女士们先生们,

我最近尝试了这个实验:

static class TryParseExtensions
{
    public delegate bool TryParseMethod<T>(string s, out T maybeValue);
    public static T? OrNull<T>(this TryParseMethod<T> tryParser, string s) where T:struct 
    {
        T result;
        return tryParser(s, out result) ? (T?)result : null;
    }
}
// compiler error "'int.TryParse(string, out int)' is a 'method', which is not valid in the given context"
var result = int.TryParse.OrNull("1");  // int.TryParse.OrNull<int>("1"); doesnt work either
// compiler error: type cannot be infered....why?
var result2 = TryParseExtensions.OrNull(int.TryParse, "2"); 
// works as expected
var result3 = TryParseExtensions.OrNull<int>(int.TryParse, "3");
      var result4 = ((TryParseExtensions.TryParseMethod<int>)int.TryParse).OrNull("4");

我想知道两件事:

  • 为什么编译器不能推断"int"类型参数?

  • 我是否正确理解扩展方法不会在Delegate类型上被发现,因为我猜它们实际上不是那种类型(但是一种"方法"),只是碰巧与Delegate签名匹配?这样的演员阵容解决了这个问题。启用场景1是否可行(当然不是具体的,而是一般的)?我想从语言/编译器的角度来看,它真的有用吗?还是我只是(试图)在这里疯狂地滥用东西?

期待一些见解。Thnx

委托作为扩展方法的第一个参数

您在这里有很多问题。(将来,我建议当你有多个问题时,把它们分成多个问题,而不是一个帖子里有几个问题;你可能会得到更好的回答。)

为什么编译器不能推断中的"int"类型参数

TryParseExtensions.OrNull(int.TryParse, "2");  

好问题。我没有在这里回答这个问题,而是让你参考我2007年的一篇文章,该文章解释了为什么这在C#3.0中不起作用:

http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

总结:从根本上说,这是一个鸡和蛋的问题。我们必须对int.TryParse进行重载解析,以确定TryParse的哪个重载是预期的重载(或者,如果它们都不起作用,错误是什么)。重载解析总是试图从参数中推断。然而,在这种情况下,这正是我们试图推断的论点类型。

我们可以提出一种新的过载解决算法,它说"好吧,如果方法组中只有一个方法,那么即使我们不知道参数是什么,也要选择那个方法",但这似乎很弱。对于只有一个方法的特殊情况方法组来说,这似乎是个坏主意,因为这样会惩罚添加新重载的用户;这可能会突然成为一个突破性的变化。

正如你从那篇文章的评论中看到的,我们得到了很多很好的反馈。得到的最好的反馈基本上是"好吧,假设类型推理已经计算出了所有参数的类型,我们正试图推断出返回类型;在这种情况下,你可以进行过载解析"。这种分析是正确的,并且对这种影响的更改进入了C#4。我在这里谈了更多:

http://blogs.msdn.com/b/ericlippert/archive/2008/05/28/method-type-inference-changes-part-zero.aspx

我是否正确地理解了扩展方法不会在委托类型上被发现,因为我猜它们实际上不是那种类型(但是一种"方法"),只是碰巧与委托签名匹配?

你的术语有点偏离,但你的想法是正确的。当"接收器"是方法组时,我们不会发现扩展方法。更一般地说,当接收器缺少自己的类型时,我们不会发现扩展方法,而是根据其上下文采用类型:方法组、lambdas、匿名方法和null文本都具有此属性。如果说null.Whatever()并在String上调用一个扩展方法,或者更奇怪的是,(x=>x+1).Whatever()并在Func<int, int>上调用扩展方法,那将是非常奇怪的。

描述这种行为的规范是:

从[接收器表达式]到第一个参数类型[…]的隐式标识、引用或装箱转换[必须存在]。

方法组上的转换不是身份转换、引用转换或装箱转换;它们是方法组转换。

启用场景1是否可行(当然不是具体的,而是一般的)?我想从语言/编译器的角度来看,它真的有用吗?还是我只是(试图)在这里疯狂地滥用东西?

这不是不可行的。我们有一个非常聪明的团队,没有理论上的理由不可能做到这一点。在我们看来,这并不是一个为语言增加更多价值的功能,而不是额外复杂性的成本。

有时它会有用。例如,我希望能够做到这一点;假设我有一个static Func<A, R> Memoize<A, R>(this Func<A, R> f) {...}:

var fib = (n=>n<2?1:fib(n-1)+fib(n-2)).Memoize();

而不是你今天必须写的东西,那就是:

Func<int, int> fib = null;
fib = n=>n<2?1:fib(n-1)+fib(n-2);
fib = fib.Memoize();

但坦率地说,所提出的功能为语言增加的额外复杂性并不是因为上面的代码不那么冗长而带来的小好处。

第一个错误的原因:
int.TryParse是一个方法组,而不是任何类型的对象实例。只能对对象实例调用扩展方法。这与以下代码无效的原因相同:

var s = int.TryParse;

这也是第二个示例中无法推断类型的原因:int.TryParse是一个方法组,而不是TryParseMethod<int>类型。

我建议您使用方法三并缩短扩展类的名称。我认为没有更好的方法可以做到这一点。

请注意,如果您首先声明:

TryParseExtensions.TryParseMethod<int> tryParser = int.TryParse;

然后在使用int.TryParse的地方使用tryParser

问题是编译器不知道你说的是int.Parse的哪个重载。所以它不能完全推断:你说的是TryParse(String, Int32)还是TryParse(String, NumberStyles, IFormatProvider, Int32)?编译器不能猜测,也不会为您任意决定(幸运的是!)。

但是您的委托类型可以明确您感兴趣的重载。这就是为什么分配tryParser不是问题。您不再谈论"方法组",而是在这组名为int.TryParse.

的方法中的一个已识别的方法签名
相关文章: