考虑以下示例。如果我首先定义该委托类型的变量,我可以调用委托的扩展方法。但我无法在作为参数传递的委托上调用该扩展方法。我不明白为什么它第一次工作,但第二次不起作用。我做错了什么?
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 + ")")
);
}
}
只要您不介意将lambdas分配给变量然后是,您可以使用此方法创建函数的部分应用程序(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
}
}
答案 0 :(得分:12)
概念没有任何问题,只有执行中的一些技术问题。
关键是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
答案 1 :(得分:10)
C#中的Lambda表达式本身没有类型。例如,您可以将lambda表达式x => x != 0
分配给Predicate<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 + ")")
);
答案 2 :(得分:7)
其他答案是正确的;我只想注意,设计团队故意选择而不是的扩展方法在任何没有类型的表达式上工作 - 所以,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
内的所有类型是。正常顺序的反转会导致编译器中出现一些非常复杂的代码,没有人急于添加另一个我们必须进行内向外推理的情况。
最后,因为显然你对currying很感兴趣,所以你可能会感兴趣。
http://blogs.msdn.com/b/ericlippert/archive/2009/06/25/mmm-curry.aspx