什么是使用委托并使用​​Func键℃之间的差异; T> /动作< T>在方法签名? [英] What is the difference between using a delegate and using Func<T>/Action<T> in a method signature?

查看:154
本文介绍了什么是使用委托并使用​​Func键℃之间的差异; T> /动作< 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 类,我已经定义了两种不同的方式:

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


  1. 一,这需要委托作为参数: ProcessPaperbackBooksWithDelegate

  2. 一个这需要相应的类型签名作为参数的一个动作: ProcessPaperbackBooksWithoutDelegate

  1. One which takes a delegate as an argument: ProcessPaperbackBooksWithDelegate
  2. One which takes an action of the corresponding type signature as argument: 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 是已经在实施例的功能。 MAIN2 Main3 是由我添加的小提琴。

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

如我所料, MAIN1 MAIN2 产生相同的结果,即:

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.



但很显然, + ISN实际上ŧ传统的功能性成分(实成分甚至没有一个行动的工作,我猜)。这一点是从事实明显,它似乎并不有一个类型签名:

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)



那么,做 + - 真的是

旁白:我试图用 VAR一个=您好; ... MAIN2 ,但得到的错误:

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.

推荐答案

由于:


  • 这样可以节省你创造了每一个可能的方法签名的自定义委托类型的麻烦。在代码中,少即是多。

  • 不同的自定义委托类型是不相容的,即使他们的签名完全匹配。您可以解决这一点,但它是冗长。

  • 自推出 Func键的动作这是写代码的惯用方式。除非有令人信服的理由,相反,你要入乡随俗办

  • 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"



最后一行是有问题的:有没有技术的原因不能正常工作,但编译器将酒吧作为不同类型的他们并禁止它。这可能会导致摩擦,因为如果你已经是一个酒吧,你会写

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



为什么在<$ C使用委托$ C>函数功能和/或动作



由于:

Why use a delegate over Func and/or Action?

Because:


  • 您的目标的C#的早期版本,这些类型是不存在的。

  • 您与复杂的功能工作签名。没有人会想键入不止一次这样更多: Func键<名单<字典< INT,串>>中的IEnumerable< IEnumerable的< INT>>>

  • You are targeting an early version of C# where these types do not exist.
  • You are working with complicated function signatures. Noone would want to type this more than once: Func<List<Dictionary<int, string>>, IEnumerable<IEnumerable<int>>>.

因为我认为这两个是很少发生的,在日常使用中的实际答案是没有任何理由。

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()

您可以从多播委托与删除委托运营商 - ,但你必须通过的完全相同的委托的中如果右手边操作数​​是不是已经多路广播委托则事的一部分。发生。例如:

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



播委托返回值



调用多播委托与非 - 无效返回类型的结果由的返回的值最后加入的成员的的多播委托。例如:

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"):

委托实例的调用列表中调用包含
多个条目通过调用在$ b每一种方法所得$ b调用列表,同步,为了。每一个所谓的方法是
通过一组相同的参数作为被给委托
实例。如果这样的委托调用包含引用参数
(§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节),一个
委托创建表达式(§7.6.10.5)和$的左手侧b $ b中的一个运营商,并且可以隐式转换为兼容的
委托类型(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;

在通俗地说这是没有意义的写变种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. 一致性好<如果另一个超载后来推出/ li>
  2. 会导致非常好的代码破损。介绍一个编译错误来调用代码 IsInteresting(INT),因为你增加了一个 IsInteresting(串)会留下一个非常不好的印象。

  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键℃之间的差异; T&GT; /动作&LT; T&GT;在方法签名?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆