14

考虑下面的例子。如果首先定义该委托类型的变量,我可以调用该委托的扩展方法。但是我不能在作为参数传递的委托上调用该扩展方法。我不明白为什么它第一次有效,但第二次无效。我究竟做错了什么?

public static class Extender
{
    public static Func<String, String> Compose(this Func<String, String> outer, Func<String, String> inner)
    {
        return input => outer(inner(input));
    }
}
public class Demo
{
    public void WillingToTakeStringToStringDelegate(Func<String, String> map)
    {
        // blah
    }
    public void RunMe()
    {
        Func<String, String> outer = x => "(outer: " + x + ")";

        // this works:
        var composition = outer.Compose(x => "(inner: " + x + ")");
        Trace.Write(composition("!"));  // ---> (outer: (inner: !))

        // this doesn't work:
        this.WillingToTakeStringToStringDelegate(
            (x => "(outer: " + x + ")").Compose(y => "(inner: " + y + ")")
        );
    }
}

更新

对于@philogon

只要您不介意必须将 lambda 分配给变量,那么可以,您可以使用此方法创建函数的部分应用程序(currying),就像老板一样:

public static class CurryingHelper
{
    public static Func<X> Apply<A, X>(this Func<A, X> fun, A a)
    {
        return () => fun(a);
    }
    public static Func<B, X> Apply<A, B, X>(this Func<A, B, X> fun, A a)
    {
        return b => fun(a, b);
    }
    public static Func<B, C, X> Apply<A, B, C, X>(this Func<A, B, C, X> fun, A a)
    {
        return (b, c) => fun(a, b, c);
    }
    public static Func<B, C, D, X> Apply<A, B, C, D, X>(this Func<A, B, C, D, X> fun, A a)
    {
        return (b, c, d) => fun(a, b, c, d);
    }

    // etc... 
}

public class Demo
{
    public void RunMe()
    {
        Func<Int32, Int32, Int32, Int32> func = (a, b, c) => a - b + c;
        var funcA1 = func.Apply(1);
        Trace.Write(funcA1(2, 3));               // --> 2
        Trace.Write(funcA1.Apply(2).Apply(3)()); // --> 2
    }
}
4

3 回答 3

13

构想没有问题,只是执行上有一些技术问题。

关键是它x => "(outer: " + x + ")"不是没有上下文的委托:它是一个 lambda 表达式,可以对应于(某种类型的)委托,甚至可以对应于表达式树。因此类型必须显式或隐式声明,例如

// this works:
this.WillingToTakeStringToStringDelegate(
    ((Func<string, string>)(x => "(outer: " + x + ")")).Compose(...)
);

这与您不能将 lambda 函数分配给隐式类型变量的原因完全相同,例如

var f1 = (string s) => "Hello " + s;                   // does not work
Func<string, string> f2 = (string s) => "Hello " + s;  // works fine
于 2013-10-22T18:11:05.710 回答
11

C# 中的 Lambda 表达式本身没有类型。例如,您可以将 lambda 表达式分配x => x != 0Predicate<int>Func<int, bool>或。Func<long, bool>YourCustomDelegate

因此,无论何时使用 lambda 表达式,都需要向编译器提供一个提示,应该使用什么委托类型。

例子:

  • 这行得通。提示是变量的类型outer

    Func<String, String> outer = x => "(outer: " + x + ")";
    
  • 这行得通。提示是方法参数的inner类型Compose

    var composition = outer.Compose(x => "(inner: " + x + ")");
    
  • 这不起作用,因为没有提供任何提示(x => "(outer: " + x + ")")

    this.WillingToTakeStringToStringDelegate(
        (x => "(outer: " + x + ")").Compose(y => "(inner: " + y + ")")
    );
    
于 2013-10-22T18:11:18.767 回答
8

其他答案是正确的;我只是想指出,设计团队故意选择扩展方法不适用于任何没有类型的表达式——因此,在 lambda、匿名方法、null 或方法组或任何动态表达式上没有扩展方法。

事实上,它走得更远。点左侧的表达式必须可通过标识隐式引用装箱转换转换为第一个参数。换句话说:

enum E { }
static class Ext
{
    public static E X(this E e) { return e; }
}

// Legal
E e1 = 0;
// Legal
E e2 = e1.X();
// No way José.
E e3 = 0.X();

这不是身份、参考或拳击转换。

这里工作的语言设计原则是第一,没有令人讨厌的惊喜。扩展方法是语言的后期添加,设计团队希望非常小心不要添加它们会以令人惊讶的方式适用的情况。

其次,在大多数情况下,C# 会从内到外对表达式类型进行推理。也就是说,当我们看到x = y我们独立分析x和y的类型,然后判断赋值是否合法。但是对于反转的无类型表达式。因为x = (y)=>{whatever}我们分析 x 的类型,然后用它来确定是否(y)=>{whatever}是合法的右手边,如果是,它是什么类型,以及里面whatever的一切都是什么类型。事物正常顺序的颠倒导致编译器中的一些非常复杂的代码,没有人急于添加另一种我们必须进行由内而外推理的情况。

最后,由于显然您对柯里化感兴趣,因此您可能会对此感兴趣。

http://blogs.msdn.com/b/ericlippert/archive/2009/06/25/mmm-curry.aspx

于 2013-10-22T21:24:53.243 回答