C# 使用委托和使用Func<;T>/行动<;T>;在方法签名中?
我一直试图用C#来说服代表们,但我似乎不明白使用它们的意义。下面是一些从“代理”页面中略作重构的代码:C# 使用委托和使用Func<;T>/行动<;T>;在方法签名中?,c#,delegates,C#,Delegates,我一直试图用C#来说服代表们,但我似乎不明白使用它们的意义。下面是一些从“代理”页面中略作重构的代码: using System; using System.Collections; namespace Delegates { // Describes a book in the book list: public struct Book { public string Title; // Title of the book.
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);
}
}
}
Main1
是示例中已有的函数Main2
和Main3
是我添加的小提琴
正如我所料,Main1
和Main2
给出了相同的结果,即:
Invoking delegate a:
Hello, A!
Invoking delegate b:
Goodbye, B!
Invoking delegate c:
Hello, C!
Goodbye, C!
Invoking delegate d:
Goodbye, D!
然而,Main3
给出了一个非常奇怪的结果:
Invoking function a: Hello, A!
Invoking function b: Goodbye, B!
Invoking function c: Goodbye, C!
Invoking function d: Goodbye, D!
如果+
实际执行的是函数合成,那么结果(对于Main3
)应该是:
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.
但是很明显,+
实际上并不是传统的功能组合(我想,真正的组合甚至对动作都不起作用)。这一点很明显,因为它似乎没有以下类型的签名:
(T2 -> T3) -> (T1 -> T2) -> T1 -> T3
相反,类型签名似乎是:
(T1 -> T2) -> (T1 -> T2) -> (T1 -> T2)
那么+
和-
到底是什么意思呢
旁白:我试图使用var a=Hello代码>在Main2
中,但出现错误:
test.cs(136,14): error CS0815: Cannot assign method group to an implicitly-typed
local variable
它可能与这个问题无关,但为什么它不能这样做呢?这似乎是一个非常直接的类型推断。自定义委托类型与Func
和Action
当您可以通过委托获得相同的结果时,为什么要使用Func
和/或Action
?
因为:
- 它省去了为每个可能的方法签名创建自定义委托类型的麻烦。在代码中,少就是多
- 不同的自定义委托类型不兼容,即使它们的签名完全匹配。你可以解决这个问题,但它是冗长的
- 自从引入
Func
和Action
以来,这就是编写代码的惯用方法。除非有令人信服的相反理由,否则你要像罗马人那样做
让我们看看问题是什么:
// 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"
最后一行是有问题的:没有技术原因说明它不能工作,但是编译器将Foo
和Bar
视为不同的类型,并且不允许它。这会导致摩擦,因为如果你只有一个条
,你就必须写
var bar = new Bar(delegate() {});
Consumer(new Foo(bar)); // OK, but the ritual isn't a positive experience
为什么在Func
和/或Action
上使用委托?
因为:
- 您的目标是C#的早期版本,其中不存在这些类型
- 您正在处理复杂的函数签名。没有人想多次键入此内容:
Func
因为我认为这两种情况都是罕见的,在日常使用中,实际的答案是“根本没有理由”。
组合多播代理
C#中的所有委托都是多播委托——也就是说,调用它们可能会调用具有该签名的任意数量的方法。操作员
+
和-
不执行函数合成;它们从多播委托中添加和删除委托。例如:
void Foo() {}
void Bar() {}
var a = new Action(Foo) + Bar;
a(); // calls both Foo() and Bar()
您可以使用操作符-
从多播委托中删除委托,但必须传入完全相同的委托。如果右侧操作数不是多播委托的一部分,则不会发生任何事情。例如:
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
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"
多播委托返回值
使用非
void
返回类型调用多播委托会导致该多播委托最后添加的成员返回的值。例如:
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
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"
当tokenisinterest
出现在源代码中时,它是一个方法组(请注意,方法组当然可以由一个方法组成,如您的示例所示)
由于您没有尝试将编译时错误转换为兼容的委托类型,因此预期会出现编译时错误(规范强制执行该错误)。更明确地解决问题:
// both of these convert the method group to the "obviously correct" delegate
Func<int, bool> f1 = IsInteresting;
Func<string, bool> f2 = IsInteresting;
//这两者都将方法组转换为“显然正确”的委托
Func f1=正在进行测试;
Func f2=正在进行测试;
用外行的话说,编写var f=isinterest
没有意义,因为编译器唯一合理的做法是创建一个委托,但它不知道应该指向哪个方法
在方法组仅包含一个方法的特殊情况下,此问题是可解的。在我脑海中,我能想到C#团队不允许它工作的两个原因:
IsInteresting(int)
的代码中引入编译错误,因为添加了IsInteresting(string)
会给人留下非常糟糕的印象自C#2.0以来,代表们就一直在场。自C#3.0以来的兰巴斯。Func和Action是.NET framework的功能,从.NET 3.5开始就存在了。Func和Action是下面的委托,只是为了方便(尽管非常方便)。它们下面的功能相同,但可以避免声明委托类型。谓词是返回bool的泛型委托,自.NET2.0以来一直存在
在编写这篇文章的过程中,有两个答案与代码解决方案,但希望您会发现这很有帮助。委托是回调方法的函数签名 Action和Func都是委托,但都是特定委托类型的缩写 操作必须有一个参数,并且不能返回值。 Func必须有一个参数,并且必须返回一个值 考虑以下代表签名:
delegate void DisplayMessage( string message);
delegate string FormatTime( DateTime date);
delegate bool IsAValidAddress( string addressLine1, string addressLine2, int postCode, string country);
第一个签名可以替换为操作
第二个签名可以替换为Func
第三个签名返回一个值,因此只能用Func
唯一的区别是委托可以通过引用传递参数,其中Func<string, string> c1 = (s) => { a(s); returns b(s); };//equivalent to c
delegate void DisplayMessage( string message);
delegate string FormatTime( DateTime date);
delegate bool IsAValidAddress( string addressLine1, string addressLine2, int postCode, string country);