使用委托和使用 Func<T>/Action<T> 有什么区别?在方法签名中?

What is the difference between using a delegate and using Funclt;Tgt;/Actionlt;Tgt; in a method signature?(使用委托和使用 Funclt;Tgt;/Actionlt;Tgt; 有什么区别?在方法签名中?)

本文介绍了使用委托和使用 Func<T>/Action<T> 有什么区别?在方法签名中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直试图了解 C# 中的委托,但我似乎不明白使用它们的意义.这是来自 MSDN 代表页面:

I have been trying to get my head around delegates in C#, but I just don't seem to get the point of using them. Here is some slightly reconstructed code from the MSDN page on delegates:

using System;
using System.Collections;

namespace Delegates
{
    // Describes a book in the book list:
    public struct Book
    {
        public string Title;        // Title of the book.
        public string Author;       // Author of the book.
        public decimal Price;       // Price of the book.
        public bool Paperback;      // Is it paperback?

        public Book(string title, string author, decimal price, bool paperBack)
        {
            Title = title;
            Author = author;
            Price = price;
            Paperback = paperBack;
        }
    }

    // Declare a delegate type for processing a book:
    public delegate void ProcessBookDelegate(Book book);

    // Maintains a book database.
    public class BookDB
    {
        // List of all books in the database:
        ArrayList list = new ArrayList();

        // Add a book to the database:
        public void AddBook(string title, string author, decimal price, bool paperBack)
        {
            list.Add(new Book(title, author, price, paperBack));
        }

        // Call a passed-in delegate on each paperback book to process it:
        public void ProcessPaperbackBooksWithDelegate(ProcessBookDelegate processBook)
        {
            foreach (Book b in list)
            {
                if (b.Paperback)
                    processBook(b);
            }
        }

        public void ProcessPaperbackBooksWithoutDelegate(Action<Book> action)
        {
            foreach (Book b in list)
            {
                if (b.Paperback)
                    action(b);
            }
        }
    }

    class Test
    {

        // Print the title of the book.
        static void PrintTitle(Book b)
        {
            Console.WriteLine("   {0}", b.Title);
        }

        // Execution starts here.
        static void Main()
        {
            BookDB bookDB = new BookDB();
            AddBooks(bookDB);
            Console.WriteLine("Paperback Book Titles Using Delegates:");
            bookDB.ProcessPaperbackBooksWithDelegate(new ProcessBookDelegate(PrintTitle));
            Console.WriteLine("Paperback Book Titles Without Delegates:");
            bookDB.ProcessPaperbackBooksWithoutDelegate(PrintTitle);
        }

        // Initialize the book database with some test books:
        static void AddBooks(BookDB bookDB)
        {
            bookDB.AddBook("The C Programming Language",
               "Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
            bookDB.AddBook("The Unicode Standard 2.0",
               "The Unicode Consortium", 39.95m, true);
            bookDB.AddBook("The MS-DOS Encyclopedia",
               "Ray Duncan", 129.95m, false);
            bookDB.AddBook("Dogbert's Clues for the Clueless",
               "Scott Adams", 12.00m, true);
        }
    }
}

正如您在 BookDB 类中看到的,我定义了 2 种不同的方法:

As you can see in the BookDB class, I have defined 2 different methods:

  1. 以委托作为参数的方法:ProcessPaperbackBooksWithDelegate
  2. 一个将相应类型签名的动作作为参数的:ProcessPaperbackBooksWithoutDelegate

调用它们中的任何一个都会返回相同的结果;那么委托解决的目的是什么?

A call to either of them returns the same result; so what purpose does a delegate solve?

同一页面上的第二个示例会导致更多混乱;这是代码:

The second example on the same page leads to lot more confusion; here is the code:

delegate void MyDelegate(string s);

static class MyClass
{
    public static void Hello(string s)
    {
        Console.WriteLine("  Hello, {0}!", s);
    }

    public static void Goodbye(string s)
    {
        Console.WriteLine("  Goodbye, {0}!", s);
    }

    public static string HelloS(string s)
    {
        return string.Format("Hello, {0}!", s);
    }

    public static string GoodbyeS(string s)
    {
        return string.Format("Goodbye, {0}!", s);
    }

    public static void Main1()
    {
        MyDelegate a, b, c, d;
        a = new MyDelegate(Hello);
        b = new MyDelegate(Goodbye);
        c = a + b;
        d = c - a;

        Console.WriteLine("Invoking delegate a:");
        a("A");
        Console.WriteLine("Invoking delegate b:");
        b("B");
        Console.WriteLine("Invoking delegate c:");
        c("C");
        Console.WriteLine("Invoking delegate d:");
        d("D");
    }

    public static void Main2()
    {
        Action<string> a = Hello;
        Action<string> b = Goodbye;
        Action<string> c = a + b;
        Action<string> d = c - a;

        Console.WriteLine("Invoking delegate a:");
        a("A");
        Console.WriteLine("Invoking delegate b:");
        b("B");
        Console.WriteLine("Invoking delegate c:");
        c("C");
        Console.WriteLine("Invoking delegate d:");
        d("D");
    }

    public static void Main3()
    {
        Func<string, string> a = HelloS;
        Func<string, string> b = GoodbyeS;
        Func<string, string> c = a + b;
        Func<string, string> d = c - a;

        Console.WriteLine("Invoking function a: " + a("A"));
        Console.WriteLine("Invoking function b: " + b("B"));
        Console.WriteLine("Invoking function c: " + c("C"));
        Console.WriteLine("Invoking function d: " + d("D"));
    }
}

Main1 是示例中已有的函数.Main2Main3 是我添加的小提琴.

Main1 is the function which was already in the example. Main2 and Main3 are fiddles added by me.

正如我所料,Main1Main2 给出相同的结果,即:

As I expected, Main1 and Main2 give the same result i.e.:

Invoking delegate a:
  Hello, A!
Invoking delegate b:
  Goodbye, B!
Invoking delegate c:
  Hello, C!
  Goodbye, C!
Invoking delegate d:
  Goodbye, D!

Main3 然而,给出了一个非常奇怪的结果:

Main3 however, gives a very strange result:

Invoking function a: Hello, A!
Invoking function b: Goodbye, B!
Invoking function c: Goodbye, C!
Invoking function d: Goodbye, D!

如果 + 实际上是在执行函数组合,那么结果(对于 Main3)应该是:

If + was actually performing function composition then the result(for Main3) should have been:

Invoking function a: Hello, A!
Invoking function b: Goodbye, B!
Invoking function c: Hello, Goodbye, C!!
Invoking function d: //God knows what this should have been.

但很明显,+ 实际上并不是传统的函数式组合(我猜,真正的组合甚至不适用于 Action).从它似乎没有类型签名的事实可以看出这一点:

But it is clear that + isn't actually the traditional functional composition(real composition wouldn't even work for an Action, I guess). That much is evident from the fact that it doesn't seem to have a type signature of:

(T2 -> T3) -> (T1 -> T2) -> T1 -> T3

相反,类型签名似乎是:

instead, the type signature seems to be:

(T1 -> T2) -> (T1 -> T2) -> (T1 -> T2)

那么+-到底是什么意思?

So what do + and - really mean?

旁白:我尝试在 Main2 中使用 var a = Hello;... 但得到错误:

Aside: I tried to use var a = Hello;... in Main2 but got the error:

test.cs(136,14): error CS0815: Cannot assign method group to an implicitly-typed
    local variable

可能与这个问题无关,但为什么不能这样做呢?这似乎是一个非常直接的类型推断.

It may not be related to this question, but why can't it do so? It seems like a pretty straight forward type deduction.

推荐答案

自定义委托类型 vs FuncAction

当您可以通过 delegate 获得相同的结果时,为什么还要使用 Func 和/或 Action?

因为:

Custom delegate types vs Func and Action

Why use Func and/or Action when you can achieve the same results with a delegate?

Because:

  • 它省去了为每个可能的方法签名创建自定义委托类型的麻烦.在代码中,少即是多.
  • 不同的自定义委托类型是不兼容的,即使它们的签名完全匹配.您可以解决这个问题,但它很冗长.
  • 自从引入 FuncAction 以来,这是编写代码的惯用方式.除非有相反的令人信服的理由,否则你想像罗马人那样做.
  • It saves you the trouble of creating a custom delegate type for each possible method signature. In code, less is more.
  • Different custom delegate types are incompatible, even if their signatures exactly match. You can work around this but it's verbose.
  • Since the introduction of Func and Action this is the idiomatic way to write code. Unless there is compelling reason for the opposite, you want to do as the Romans do.

让我们看看问题是什么:

Let's see what the problem is:

// Delegates: same signature but different types
public delegate void Foo();
public delegate void Bar();

// Consumer function -- note it accepts a Foo
public void Consumer(Foo f) {}

试一试:

Consumer(new Foo(delegate() {})); // works fine
Consumer(new Bar(delegate() {})); // error: cannot convert "Bar" to "Foo"

最后一行是有问题的:没有技术原因导致它无法工作,但编译器将 FooBar 视为不同的类型并禁止它.这可能会导致摩擦,因为如果您只有一个 Bar 您将不得不编写

The last line is problematic: there is no technical reason why it cannot work, but the compiler treats Foo and Bar as the distinct types they are and disallows it. This can lead to friction because if all you have is a Bar you would have to write

var bar = new Bar(delegate() {});
Consumer(new Foo(bar)); // OK, but the ritual isn't a positive experience

为什么在 Func 和/或 Action 上使用委托?

因为:

Why use a delegate over Func and/or Action?

Because:

  • 您的目标是早期版本的 C#,其中不存在这些类型.
  • 您正在处理复杂的函数签名.没有人会想要多次输入:Func.

由于我认为这两种情况都很少发生,因此在日常使用中,实际的答案是完全没有理由".

Since I consider both of these as rare occurrences, in everyday usage the practical answer is "no reason at all".

C# 中的所有委托都是多播委托——也就是说,调用它们可能会调用具有该签名的任意数量的方法.运算符 +- 不执行函数组合;他们从多播委托中添加和删除委托.一个例子:

All delegates in C# are multicast delegates -- that is, invoking them can potentially invoke any number of methods with that signature. The operators + and - do not perform function composition; they add and remove a delegate from a multicast delegate. An example:

void Foo() {}
void Bar() {}

var a = new Action(Foo) + Bar;
a(); // calls both Foo() and Bar()

您可以使用 operator- 从多播委托中删除委托,但您必须传递完全相同的委托.如果右侧操作数尚未多播委托的一部分然后什么也没有发生.例如:

You can remove a delegate from a multicast delegate with operator-, but you must pass the exact same delegate in. If right-hand-side operand was not already part of the multicast delegate then nothing happens. For example:

var a = new Action(Foo);
a();      // calls Foo()
a -= Bar; // Bar is not a part of the multicast delegate; nothing happens
a();      // still calls Foo() as before

多播委托返回值

使用非void 返回类型调用多播委托会导致多播委托的最后添加的成员返回值.例如:

Multicast delegate return values

Invoking a multicast delegate with a non-void return type results in the value returned by the last added member of the multicast delegate. For example:

public int Ret1() { return 1; }
public int Ret2() { return 2; }

Console.WriteLine((new Func<int>(Ret1) + Ret2)()); // prints "2"
Console.WriteLine((new Func<int>(Ret2) + Ret1)()); // prints "1"

这在 C# 规范中记录(第 15.4 节,委托调用"):

This is documented in the C# spec (§15.4, "delegate invocation"):

调用一个委托实例,其调用列表包含多个条目通过调用调用列表,同步,按顺序.所谓的每种方法都是传递了与委托相同的一组参数实例.如果此类委托调用包含引用参数(§10.6.1.2),每个方法调用都会引用同一个变量;通过一种方法更改该变量调用列表将对调用下方的方法可见列表.如果委托调用包含输出参数或返回值,它们的最终值将来自于列表中的最后一位代表.

Invocation of a delegate instance whose invocation list contains multiple entries proceeds by invoking each of the methods in the invocation list, synchronously, in order. Each method so called is passed the same set of arguments as was given to the delegate instance. If such a delegate invocation includes reference parameters (§10.6.1.2), each method invocation will occur with a reference to the same variable; changes to that variable by one method in the invocation list will be visible to methods further down the invocation list. If the delegate invocation includes output parameters or a return value, their final value will come from the invocation of the last delegate in the list.

旁白:不能将方法组分配给隐式类型的局部变量"

首先你需要知道什么是方法组.规范说:

Aside: "Cannot assign method group to an implicitly-typed local variable"

First of all you need to know what a method group is. The specification says:

一个方法组,它是一组重载的方法,由一个成员查找(第 7.4 节).[...] 方法组是在调用表达式(第 7.6.5 节)中允许,a委托创建表达式(第 7.6.10.5 节)并作为is 运算符,并且可以隐式转换为兼容的委托类型(§6.6).在任何其他情况下,分类的表达作为方法组会导致编译时错误.

A method group, which is a set of overloaded methods resulting from a member lookup (§7.4). [...] A method group is permitted in an invocation-expression (§7.6.5), a delegate-creation-expression (§7.6.10.5) and as the left hand side of an is operator, and can be implicitly converted to a compatible delegate type (§6.6). In any other context, an expression classified as a method group causes a compile-time error.

所以,给定一个具有这两种方法的类:

So, given a class with these two methods:

public bool IsInteresting(int i) { return i != 0; }
public bool IsInteresting(string s) { return s != ""; }

当标记 IsInteresting 出现在源代码中时,它是一个方法组(请注意,一个方法组当然可以由一个方法组成,如您的示例所示).

When the token IsInteresting appears in the source, it's a method group (note that a method group can of course consist of one single method, as in your example).

编译时错误是预期的(规范要求它),因为您没有尝试将其转换为兼容的委托类型.更明确地解决问题:

The compile-time error is expected (the spec mandates it) because you are not trying to convert it to a compatible delegate type. Being more explicit solves the problem:

// both of these convert the method group to the "obviously correct" delegate
Func<int, bool> f1 = IsInteresting;
Func<string, bool> f2 = IsInteresting;

通俗地说,写 var f = IsInteresting 是没有意义的,因为编译器唯一合理的做法是创建一个委托,但它不知道应该指向哪个方法.

In layman's terms it's not meaningful to write var f = IsInteresting because the only reasonable thing for the compiler would be to create a delegate, but it does not know which method it should point to.

在方法组只包含一个方法的特殊情况下,这个问题是可以解决的.我能想到 C# 团队不允许它工作的两个原因:

In the special case where the method group contains exactly one method this problem is solvable. Off the top of my head I can think of two reasons why the C# team did not allow it to work:

  1. 一致性很好.
  2. 如果稍后引入另一个重载,将导致完美的代码被破坏.向调用 IsInteresting(int) 的代码引入编译错误,因为您添加了 IsInteresting(string) 会留下非常糟糕的印象.
  1. Consistency is good.
  2. Would lead to breakage of perfectly good code if another overload is introduced later. Introducing a compile error to code that calls IsInteresting(int) because you added an IsInteresting(string) would leave a really bad impression.

这篇关于使用委托和使用 Func&lt;T&gt;/Action&lt;T&gt; 有什么区别?在方法签名中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:使用委托和使用 Func&lt;T&gt;/Action&lt;T&gt; 有什么区别?在方法签名中?

基础教程推荐