我一直在尝试用C#代替我的代表,但我似乎没有明白使用它们。以下是代表们MSDN页面上的一些略微重构的代码:
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
课程中所看到的,我已经定义了两种不同的方法:
ProcessPaperbackBooksWithDelegate
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("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
和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
这可能与此问题无关,但为什么不能这样做呢?这似乎是一种非常直接的类型演绎。
答案 0 :(得分:32)
Func
和Action
Func
获得相同结果时使用Action
和/或delegate
?由于:
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
视为不同的类型并禁止它。这可能会导致摩擦,因为如果你只有Bar
,你就必须写
var bar = new Bar(delegate() {});
Consumer(new Foo(bar)); // OK, but the ritual isn't a positive experience
Func
和/或Action
?由于:
Func<List<Dictionary<int, string>>, IEnumerable<IEnumerable<int>>>
。由于我认为这两种情况都很少发生,在日常使用中,实际答案是“毫无理由”。
C#中的所有代理都是多播委托 - 也就是说,调用它们可能会使用该签名调用任意数量的方法。运算符+
和-
不执行函数组合;他们从多播委托中添加和删除委托。一个例子:
void Foo() {}
void Bar() {}
var a = new Action(Foo) + Bar;
a(); // calls both Foo() and Bar()
您可以使用operator-
从多播委托中删除委托,但必须传递完全相同的委托。如果右侧操作数不是多播的一部分代表然后什么都没发生。例如:
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
返回类型的多播委托会导致多播委托的上次添加的成员返回的值。例如:
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节,“委托调用”)中有记录:
调用其调用列表包含的委托实例 通过调用中的每个方法继续多个条目 调用列表,按顺序同步。所谓的每种方法都是 传递给代表的同一组参数 实例。如果这样的委托调用包括引用参数 (§10.6.1.2),每个方法调用都会在引用时发生 相同的变量;通过一个方法更改该变量 调用列表将在调用后面的方法中可见 名单。 如果委托调用包含输出参数或a 返回值,它们的最终值将来自于调用 列表中的最后一位代表。
首先,您需要知道方法组是什么。规范说:
一个方法组,它是一组由a产生的重载方法 成员查找(第7.4节)。 [...]方法组是 在invocation-expression(第7.6.5节)中允许,a delegate-creation-expression(§7.6.10.5)和左侧
is
运算符,可以隐式转换为兼容运算符 委托类型(§6.6)。在任何其他上下文中,表达式分类 作为方法组会导致编译时错误。
所以,给定一个具有这两种方法的类:
public bool IsInteresting(int i) { return i != 0; }
public bool IsInteresting(string s) { return s != ""; }
当令牌IsInteresting
出现在源中时,它是一个方法组(请注意,方法组当然可以包含一个方法,如您的示例所示)。
编译时错误是预期的(规范要求它)因为您没有尝试将其转换为兼容的委托类型。更明确地解决了这个问题:
// 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
没有意义,因为编译器唯一合理的事情就是创建一个委托,但它不知道应该指向哪个方法。
在方法组只包含一个方法的特殊情况下,此问题是可解决的。在我的脑海中,我可以想到为什么C#团队不允许它工作的两个原因:
IsInteresting(int)
的代码,因为您添加了IsInteresting(string)
会给人留下非常糟糕的印象。答案 1 :(得分:3)
委托是回调方法的函数签名。
Action和Func都是委托,但是特定委托类型的简写。
操作必须有一个参数,不得返回值。 Func必须有一个参数,并且必须返回一个值。
考虑以下代理签名:
delegate void DisplayMessage( string message);
delegate string FormatTime( DateTime date);
delegate bool IsAValidAddress( string addressLine1, string addressLine2, int postCode, string country);
第一个签名可以替换为Action<T>
第二个签名可以替换为Func<T, TResult>
第三个签名返回一个值,因此只能用Func<T1, T2, T3, T4, TResult>
唯一的区别是委托可以通过引用传递参数,其中Action
和Func
只能按值传递参数
玩得开心。
答案 2 :(得分:0)
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"));
c("C")
执行a("C")
然后b("C")
和返回最后Func
的结果,即b
;
Func<string, string> c1 = (s) => { a(s); returns b(s); };//equivalent to c
答案 3 :(得分:0)
自C#2.0以来,代表们一直存在。自C#3.0以来的Lambdas。 Func和Action是.NET框架的特性,自.NET 3.5起就已存在。 Func和Action是下面的代表,仅仅是为了方便(尽管非常方便)。它们在功能上相同,但是可以避免声明委托类型。 Predicate是返回bool的通用委托,自.NET 2.0以来就一直存在。
在编写本文时,已经有2个代码解决方案的答案,但希望您觉得这很有帮助。