使用代理和使用Func< T> / Action< T>之间的区别是什么?在方法签名? [英] What is the difference between using a delegate and using Func<T>/Action<T> in a method signature?

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

问题描述

我一直试图让C#中的代表团结起来,但是我似乎并没有明白使用它们。以下是 MSDN 代表页面:

  using System; 
使用System.Collections;

命名空间委托
{
//描述图书列表中的一本书:
public struct Book
{
public string Title; //书名。
public string Author; //书的作者
public decimal价格; //这本书的价格。
public bool平装本; //是平装书吗?

public Book(string title,string author,decimal price,bool paperBack)
{
Title = title;
作者=作者;
价格=价格;
平装本= paperBack;
}
}

//声明一个委托类型来处理一本书:
public delegate void ProcessBookDelegate(Book book);

//维护书籍数据库。
public class BookDB
{
//数据库中所有图书的列表:
ArrayList list = new ArrayList();

//向数据库添加一本书:
public void AddBook(string title,string author,decimal price,bool paperBack)
{
list.Add新书(标题,作者,价格,纸回));
}

//在每本平装书上调用一个传入代理来处理它:
public void ProcessPaperbackBooksWithDelegate(ProcessBookDelegate processBook)
{
foreach (列表中的b)
{
if(b.Paperback)
processBook(b);
}
}

public void ProcessPaperbackBooksWithoutDelegate(Action< Book> action)
{
foreach(列表中的b)
{
if(b.Paperback)
action(b);
}
}
}

class Test
{

//打印书名。
static void PrintTitle(Book b)
{
Console.WriteLine({0},b.Title);
}

//执行从这里开始。
static void Main()
{
BookDB bookDB = new BookDB();
AddBooks(bookDB);
Console.WriteLine(平装书名称使用代表:);
bookDB.ProcessPaperbackBooksWithDelegate(new ProcessBookDelegate(PrintTitle));
Console.WriteLine(没有代表的平装书标题:);
bookDB.ProcessPaperbackBooksWithoutDelegate(PrintTitle);
}

//用一些测试书初始化书籍数据库:
static void AddBooks(BookDB bookDB)
{
bookDB.AddBook(The C编程语言,
Brian W. Kernighan和Dennis M. Ritchie,19.95m,true);
bookDB.AddBook(Unicode Standard 2.0,
Unicode Consortium,39.95m,true);
bookDB.AddBook(MS-DOS百科全书,
Ray Duncan,129.95m,false);
bookDB.AddBook(Dogbert's Clues for the Clueless,
Scott Adams,12.00m,true);
}
}
}

正如你在 BookDB class,我已经定义了两种不同的方法:


  1. 作为参数: ProcessPaperbackBooksWithDelegate

  2. 一个采取相应类型签名作为参数的操作: ProcessPaperbackBooksWithoutDelegate

对其中任一个的调用返回相同的结果;所以代表的目的是什么呢?



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

  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(调用委托a:);
a(A);
Console.WriteLine(调用委托b:);
b(B);
Console.WriteLine(调用委托c:);
c(C);
Console.WriteLine(调用委托d:);
d(D);
}

public static void Main2()
{
Action< string> a =你好
操作< string> b =再见
操作< string> c = a + b;
操作< string> d = c-a;

Console.WriteLine(调用委托a:);
a(A);
Console.WriteLine(调用委托b:);
b(B);
Console.WriteLine(调用委托c:);
c(C);
Console.WriteLine(调用委托d:);
d(D);
}

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

Console.WriteLine(调用函数a:+ a(A));
Console.WriteLine(调用函数b:+ b(B));
Console.WriteLine(调用函数c:+ c(C));
Console.WriteLine(调用函数d:+ d(D));
}
}

Main1 是已经在示例中的函数。 Main2 Main3 是我添加的Fiddles。



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

 调用委托a:
你好,A!
调用代理b:
再见,B!
调用委托c:
你好,C!
再见,C!
调用代理d:
再见,D!

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

 调用函数a:你好,A! 
调用函数b:再见,B!
调用函数c:再见,C!
调用函数d:再见,D!

如果 + 正在执行功能组合结果(对于 Main3 )应该是:

 调用函数a你好啊
调用函数b:再见,B!
调用函数c:你好,再见,C!
调用函数d://上帝知道这应该是什么。

但很明显, + isn'实际上,传统的功能组合(实际组合甚至不会适用于一个动作,我猜)。这很明显,因为它似乎没有类型签名:

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

相反,类型签名似乎是:

 (T1→T2)→> (T1→T2)→> (T1→T2)

那么,$ code> + 和 - 真的是吗?



Aside:我试图使用 var a = Hello; ... in Main2 但收到错误:

  test.cs(136,14):错误CS0815:无法将方法组分配给隐式类型的
本地变量

它可能与此问题无关,但为什么不这样做?

解决方案

自定义委托类型与 Func Action



为什么要使用 Func /或 Action 当您可以通过委托获得相同的结果



因为:




  • 它为您节省了为每个可能的方法签名创建自定义委托类型的麻烦。在代码中,少于更多。

  • 即使他们的签名完全匹配,不同的自定义委托类型是不兼容的。

  • Func Action 这是编写代码的惯用方式。除非有令人信服的理由,否则你想像罗马人那样做。



我们来看看是什么问题: p>

  //代理:相同的签名但不同的类型
public delegate void Foo();
public delegate void Bar();

//消费者函数 - 注意它接受一个Foo
public void Consumer(Foo f){}

尝试一下:

  Consumer(new Foo(delegate(){}) ); // works fine 
Consumer(new Bar(delegate(){})); //错误:无法将Bar转换为Foo

最后一行是有问题的:有没有技术原因为什么它不能工作,但是编译器将 Foo Bar 作为它们的不同类型,不允许。这可能会导致摩擦,因为如果你所有的都是一个 Bar ,你将不得不写

 code> var bar = new Bar(delegate(){}); 
消费者(新Foo(酒吧)); // OK,但仪式不是一个积极的经验



为什么使用代理超过 Func 和/或动作



因为:




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

  • 您正在使用复杂的功能签名。没有人想要多次输入: Func< List< Dictionary< int,string>>,IEnumerable< IEnumerable< int>> 。 >


由于我认为这两个都是罕见的事件,在日常使用中,实际的答案是根本没有理由。



组播多播代理



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

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

var a = new Action(Foo)+ Bar;
a(); //调用Foo()和Bar()

您可以从多播代理中删除代理 operator - ,但是您必须传递完全相同的委托。如果右边的操作数不是组播委托的一部分,那么没有发生。例如:

  var a = new Action(Foo); 
a(); // call Foo()
a - = Bar; // Bar不是组播委托的一部分;没有任何事情
a(); //仍然像以前一样调用Foo()



组播委托返回值



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

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

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

这在C#规范(§15.4,委托调用 )


调用调用列表包含
的多个条目的委托实例的调用通过调用$ b中的每个方法$ b调用列表,按顺序同步。所谓的每个方法是
传递与给定代理
实例相同的一组参数。如果这样一个委托调用包含参考参数
(§10.6.1.2),每个方法的调用都会引用
相同的变量;
调用列表中的一个方法对该变量的更改对于进一步调用
列表的方法将可见。 如果委托调用包含输出参数或
返回值,则其最终值将来自调用列表
中的
最后一个委托。




Aside:无法将方法组分配给隐式类型的本地变量



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


一个方法组,它是由
成员查找(§7.4)产生的一组重载方法, 。 [...]方法组在调用表达式(§7.6.5)中允许

委托创建表达式(§7.6.10.5),左侧为
an 运算符,并且可以隐式转换为兼容的
委托类型(§6.6)。在任何其他上下文中,将
分解为方法组的表达式导致编译时错误。


因此,这两个方法的类:

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

当令牌 IsInteresting 出现在来源,它是一个方法组(注意,方法组当然可以由一个单一的方法组成,如您的示例)。



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

  //这两个都将方法组转换为显然正确的代理
Func< int,bool> f1 = IsInteresting;
Func< string,bool> f2 = IsInteresting;

以外行人的术语写入 var f = IsInteresting 因为编译器唯一合理的事情是创建一个委托,但不知道应该指出哪个方法。



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


  1. 一致性是好的。 / li>
  2. 如果稍后引入另外一个超载,会导致完美的代码破损。对于调用 IsInteresting(int)的代码引入编译错误,因为您添加了一个 IsInteresting(string)将会真的不好的印象。


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);
        }
    }
}

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

  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 is the function which was already in the example. Main2 and Main3 are fiddles added by me.

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 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!

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.

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?

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.

解决方案

Custom delegate types vs Func and Action

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

Because:

  • 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) {}

Trying it out:

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

Why use a delegate over Func and/or Action?

Because:

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

Composing multicast delegates

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"

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

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:

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 != ""; }

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;

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.

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. 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;之间的区别是什么?在方法签名?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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