有趣的我应该什么时候使用它们的用途

时间:2009-06-10 07:29:42

标签: c# .net generics lambda functor

我似乎无法绕过他们。

据我了解,它正在动态地为类添加逻辑。框架内的课程是否为此做好准备?

为什么我应该扩展类并在扩展中为其添加功能。我可以在全球范围内访问,并且更容易维护。

我读过有4种函子类型:

的Comparer
关闭
谓词
变压器

我们应该处理它们中的每一个。

P.S。在vb中有类似的东西吗?

所以我可以说我认为lambda表达式是仿函数。这为我清理了一些东西:)(呵呵)

  • Lambda表达式是算子吗?
  • 匿名函数是函子吗?

但是我问了这个问题,因为我遇到了另一种类型的问题,即这些问题:

delegate void FunctorDelegate(int value);
class Addition {
    FunctorDelegate _delegate;

    public Addition AddDelegate(FunctorDelegate deleg) {
        _delegate += deleg;
        return this;
    }
    public int AddAllElements(IList< int> list) {
        int runningTotal = 0;
        foreach( int value in list) {
            runningTotal += value;
            _delegate(value);
        }
        return runningTotal;
    }
}

然后用它来调用它:

 int runningTotal = new Addition()
     .AddDelegate(new FunctorDelegate(
                     delegate(int value) {
                         if ((value % 2) == 1) {
                             runningOddTotal += value;
                         }
                     }))
    .AddDelegate(new FunctorDelegate(
                     delegate(int value) {
                         if ((value % 2) == 0) {
                             runningEvenTotal += value;
                         }
                     }))
    .AddAllElements(list);

所以没有花哨的lambda风格的东西。

现在我有了这个例子,但是为什么这是一个“好”的解决方案并不清楚。

作为lambda表达式或匿名方法使用的委托(仿函数)“在大多数情况下”只是作为程序员的快捷方式吗?据我所知,只有少数情况下他们实际上是问题的首选。

5 个答案:

答案 0 :(得分:22)

我认为你混淆了不同语言的术语。您似乎在C ++或Java意义上使用“Functor”,例如see the wikipedia page。在C ++中,它是一个类的对象,它重载了函数调用操作符,因此它可以用作函数但是具有状态。

这在逻辑上与绑定到C#(或任何.NET语言)中的实例方法的委托相同。

有三种方法可以写出这样的东西。首先,您可以编写一个普通方法,然后将该方法的名称分配给委托变量。

void MyMethod() { Console.WriteLine("Hi!"); }

void Foo()
{
    Action a = MyMethod;
    a();
}

其次,您可以使用C#2.0中引入的匿名方法语法:

void Foo()
{
    Action a = delegate { Console.WriteLine("Hi!"); }
    a();
}

第三,您可以使用C#3.0中引入的lambda语法:

void Foo()
{
    Action a = () => Console.WriteLine("Hi!");
    a();
}

最后两个的优点是方法的主体可以在包含方法中读取和写入局部变量。

lambda语法相对于anon方法的优势在于它更简洁,并且可以对参数进行类型推断。

更新: anon-methods(delegate关键字)优于lambdas的优点是,如果您不需要,可以完全省略参数:

// correct way using lambda
button.Click += (sender, eventArgs) => MessageBox.Show("Clicked!");

// compile error - wrong number of arguments
button.Click += () => MessageBox.Show("Clicked!");

// anon method, omitting arguments, works fine
button.Click += delegate { MessageBox.Show("Clicked!"); };

我只知道一个值得了解的情况,即在初始化事件时,您不必在解雇之前检查null

event EventHandler Birthday = delegate { };

在其他地方避免很多废话。

最后,你提到有四种仿函数。事实上,有一些无限可能的委托类型,虽然有些作者可能有他们的最爱,显然会有一些共同的模式。 ActionCommand不接受任何参数并返回void,谓词采用某种类型的实例并返回truefalse

在C#3.0中,您可以使用您喜欢的任何类型的最多四个参数来创建一个委托:

Func<string, int, double> f;  // takes a string and an in, returns a double

重新:更新问题

你问(我认为)是否有很多lambda用例。有可能列出更多!

您最常见的是在序列上运行的较大表达式(即时计算列表)中间。假设我有一份人员名单,我想要一份四十岁的人员名单:

var exactlyForty = people.Where(person => person.Age == 40);

Where方法是IEnumerable<T>界面上的一种扩展方法,其中T在某种情况下是某种Person类。

这在.NET中称为“Linq to Objects”,但在其他地方称为序列或流上的纯函数式编程或“懒惰”列表(同一事物的所有不同名称)。

答案 1 :(得分:7)

在.NET术语中,我认为你所描述的是Delegate - 它存在于所有的.NET中,而不仅仅是C#。

我不确定“闭包”是否会与比较器/谓词/变换器中的“类型”相同,因为在C#术语中,闭包只是一个实现细节,但可以 这三个中的任何一个。

在.NET中,代理主要有两种使用方式:

  • 作为事件发生机制
  • 提供功能式编程

第一个很重要,但听起来你对第二个更感兴趣。实际上,它们的运行方式与单方法接口非常相似......考虑一下:

List<int> vals = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> evenVals = vals.FindAll(i => i % 2 == 0); // predicate
List<string> valsAsStrings = vals.ConvertAll(i => i.ToString()); // transformer
// sort descending
vals.Sort((x, y) => y.CompareTo(x)); // comparer

我们将更多范围从外部委托带入委托,我们需要更多关闭:

int max = int.Parse(Console.ReadLine()); // perhaps 6
List<int> limited = vals.FindAll(i => i <= max);

此处max作为闭包被捕获到委托中。

Re“框架内的课程是否为此准备好了?” - 很多都是,LINQ采用 long 方式来实现更广泛的目标。 LINQ提供了(例如)所有IEnumerable<T>的扩展方法 - 这意味着没有基于委托的访问的集合可以免费获取它们:

int[] data = { 1,2,3,4,5,6,7,8,9 };
var oddData = data.Where( i => i % 2 == 1 );
var descending = data.OrderBy(i => -i);
var asStrings = data.Select(i => i.ToString());

此处WhereOrderBy方法是LINQ扩展方法,需要代理。

答案 2 :(得分:1)

对术语感兴趣;我对“Functor”一词的自发解释是它提到了匿名方法。所以这将是我对它的看法。

这些是我的一些典型用途:

比较(通常用于排序列表):

List<int> ints = new List<int>();
ints.AddRange(new int[] { 9, 5, 7, 4, 3, 5, 3 });
ints.Sort(new Comparison<int>(delegate(int x, int y)
    {
        return x.CompareTo(y);
    }));
// yes I am aware the ints.Sort() would yield the same result, but hey, it's just
// a conceptual code sample ;o)

// and the shorter .NET 3.5 version:
ints.Sort((x, y) =>
{
    return x.CompareTo(y);
});

我将使用这种方法进行比较,而不是在自己的方法中使用该方法的委托,在这种特殊排序仅在一个地方发生的情况下。如果我可能想在其他地方使用相同的比较,那么它就会生活在自己的,可重复使用的方法中。

我的另一个相当常见的用途是在单元测试中,当测试依赖于某些事件被引发时。我发现在Workflow Foundation中单元测试工作流程时必不可少:

WorkflowRuntime runtime = WorkflowHost.Runtime;  
WorkflowInstance instance = runtime.CreateWorkflow(typeof(CreateFile)); 
EventHandler<WorkflowEventArgs> WorkflowIdledHandler = delegate(object sender, WorkflowEventArgs e)
{
    // get the ICreateFileService instance from the runtime  
    ISomeWorkflowService service = WorkflowHost.Runtime.GetService<ISomeWorkflowService>();

    // set the desired file content  
    service.DoSomeWork(instance.InstanceId, inputData);
};  
// attach event handler
runtime.WorkflowIdled += WorkflowIdledHandler;  

instance.Start();  
// perform the test, and then detach the event handler
runtime.WorkflowIdled -= WorkflowIdledHandler; 

在这种情况下,将事件处理程序声明为匿名方法更简单,因为它使用在单元测试方法范围中定义的instance变量。如果我选择将事件处理程序作为自己独立的方法来实现,我还需要找到一种方法来获取instance,可能是通过引入一个类级成员,这似乎不是一个完美的设计在单元测试课程中。

我的代码中有更多的情况可以找到它,但它们通常有一两个共同点:

  • 我没有兴趣从那个地方引用那段代码而不是那个地方
  • 该方法需要访问超出常规方法范围的数据

答案 3 :(得分:1)

真正的答案是,仿函数是一种数学对象,并且是一种具体的#34;用不同的语言以不同的方式。例如,假设您有一个&#34;容器&#34;存储一堆相同类型的其他对象的对象。 (例如,集合或数组)然后,如果您的语言有一种方法可以让您映射&#39;在容器上,以便您可以在容器中的每个对象上调用方法,然后容器将是一个仿函数

换句话说,仿函数是一个容器,其方法允许您将方法传递给其包含的东西。

每种语言都有自己的方法,有时会混淆使用方式。例如,C ++使用函数指针来表示&#34;传入&#34;一个方法,并调用函数指针a&#34;仿函数。&#34;代表只是你可以传入的方法的句柄。你正在使用术语&#34;错误地#34;,就像C ++一样。

Haskell做对了。您声明类型实现了functor接口,然后您获得了映射方法。

函数(如lambdas)也是函子,但是很难将函数视为&#34;容器&#34;。简而言之,一个功能是一个容器&#34;围绕返回值,以这样的方式构造,使得返回值(可能)取决于函数的参数。

答案 4 :(得分:0)

我确定你的意思是Lambda表达式。那些是你可以写得很快的小函数,它们具有“=&gt;”的特征操作员。这些是C#3.0的新功能。

这个例子将是一个经典的变形金刚;使用一个我们需要一个委托来定义Lambda函数的签名。

delegate int Transformer(int i);

现在使用此委托声明Lambda:

Transformer sqr = x => x * x;

我们可以像普通函数一样使用它:

Console.WriteLine(sqr(3)); //9

这些在LINQ查询中使用很多,例如Sort(Comparer),搜索(Predicate)。

“C#Pocket Reference”这本书(除了我认为最好的一面,在Lambdas中有很好的作用。(ISBN 978-0-596-51922-3)