我理解委托和事件的工作方式。我还可以想象一些我们应该实现事件的常见场景,但是我很难理解在什么情况下应该使用委托。
感谢名单
回复用户KVB的帖子:
A)
除非您使用单方法接口,否则基本上可以使用委托。
我想我有点理解以下内容:
类 C 可以定义方法 C.M ,它将接口 IM 作为参数。此界面将定义方法 IM.A ,因此任何想要调用 C.M 的人都需要实现此接口。
或者,方法 CM 可以(而不是界面 IM )作为参数 D 具有相同的签名作为方法 IM.A 。
但我不明白的是,即使我们的界面 IM , CM 也不能将委托 D 用作参数除了方法 A 之外还定义了其他几种方法?因此, C 类的其他方法可能需要接口 IM 作为参数,但 CM 可能需要委托 D < / strong>(假设 CM 只需要调用方法 A 而不是 IM 中定义的任何其他方法)?
b)
var list = new List<int>(new[] { 1, 2, 3 });
var item = list.Find(i => i % 2 == 0);
以上代码是用户 jpbochi 调用(请参阅此帖子中的帖子/他的帖子)依赖注入的示例吗?
我认为上述代码无法使用事件而不是“纯”代理来实现?
答案 0 :(得分:6)
当您想要提供一个函数时,将在某个事件上执行。您为事件的事件处理程序提供了即将执行的函数的委托。当你进行事件驱动编程时,它们很棒。
此外,当您具有函数作为参数(LINQ表达式,谓词,映射函数,聚合函数等)时。此功能通常称为更高级别的功能。
此外,当调用者不需要访问实现该方法的对象上的其他属性,方法或接口时,您可以包装一些功能。在这种情况下,它会以某种方式替换继承。
答案 1 :(得分:5)
在我看来,代表是最简单的依赖注入方式。当组件接收委托时(在事件中或在常规方法中),它允许另一个组件向其注入行为。
答案 2 :(得分:4)
除非您使用单方法接口,否则基本上可以使用委托。虽然这并不总是合适的,但通常使用委托代替接口可以大大提高可读性,因为逻辑更接近于使用它的位置。例如,哪些例子更容易理解并检查是否正确?
var list = new List<int>(new[] { 1, 2, 3 });
var item = list.Find(i => i % 2 == 0);
相反:
var list = new List<int>(new[] { 1, 2, 3 });
list.Find(new DivisibleBy2Finder());
// Somewhere far away
private class DivisibleBy2Finder : IFinder<int> {
public bool Matches(int i) {
return i % 2 == 0;
}
}
<强>更新强>
让我稍微扩展一下我的答案。从概念上讲,委托非常类似于具有特殊语法的单方法接口,用于在不使用其名称的情况下调用方法(即,给定委托D
,您可以通过语法D()
调用其方法)。还有另外两件事使代表比单方法接口更有趣:
Action<string>
委托:Action<string> action = new Action<string>(Console.WriteLine);
。这将创建一个委托,在将字符串传递给控制台时将其参数打印到控制台。虽然这允许您有效地传递方法,但是必须已经在某些类上定义了这些方法。事件和代表之间的关系有点棘手。尽管事件是按照代表的方式实现的,但我不确定这是考虑它们的最佳方式。与其他类型的实例一样,代表可以在许多不同的环境中使用;它们可以是类的成员,它们可以传递给方法或从方法返回,它们可以存储在方法中的局部变量等中。另一方面,事件是支持以下操作的类的特殊成员:
因此,事件经常暴露在类上以允许其他组件注册回调,这些回调将在需要时从事件的类中调用。但是,代表可以在更广泛的情况下使用。例如,在基类库中,它们经常被用作方法的参数,以对集合执行泛型操作。
希望有助于澄清一些事情。
答案 3 :(得分:3)
实现回调和事件监听器。
例如,如果您有一个执行远程请求的功能(例如,检索您的Facebook好友列表),那么您可以将委托作为最终参数传递给该功能,并在收到服务器响应后执行该操作。
答案 4 :(得分:2)
异步回调是另一个很好的例子
答案 5 :(得分:1)
委托用于将比较函数传递给泛型排序例程;代表可能被用来实施战略模式;和委托用于调用异步方法,以及其他用途。
编辑添加:
委托和事件之间的这种比较在某种程度上并没有任何意义。这就像在我们将字段标记为“公共”时询问为什么我们需要整数。
C#中的event
只不过是对委托类型字段的访问限制。它基本上说,另一个类或对象可以访问该字段以进行添加和删除,但可能不会检查该字段的内容或使批发更改为该字段的值。但事件的类型始终是委托,并且委托有许多用途,不需要事件机制提供的访问限制。
答案 6 :(得分:1)
我使用委托来保留我的对象和类库loosely coupled。
例如:
我在MainForm上有两个控件TabControlA和TabControlB。它们的代码驻留在独立的库中,这些库是MainForm的依赖项。
TabControlA有一个公共SetShowMessage方法,它将名为ShowMessage的私有成员设置为任何委托(类型为Action&lt; string&gt;,比如说)。
当MainForm加载时,它可以通过调用TabControlA.SetShowMessage(TabControlB.PrettyShowingFunction)来设置事物来连接TabControlB(只是那部分)TabControlA。
现在在内部,TabControlA可以检查ShowMessage是否为非null并调用ShowMessage(“Hurray,将在TabControlB上显示的消息!”),现在调用TabControlB.PrettyShowingFunction,允许TabControlA与TabControlB通信,可以显示此消息。
这可以扩展为允许TabControlC做同样的事情并在TabControlB等上显示消息。
我不知道这叫什么,但我认为这是the Mediator Pattern。使用Mediator对象,您可以将更多代理捆绑在一起,例如MainForm上的进度表和状态标签,任何控件都可以更新。
答案 7 :(得分:1)
我喜欢代表和事件(他们齐头并进)以分离关注(SOC)。
什么是代表?简而言之,委托是一种类型安全的方法签名。事件基本上存储对方法集合的引用...事件和委托提供了一种向多个消费者提供上下文更改通知的方法......
发布商呼叫事件和订阅者接收通知。
这是如何工作的?这是一个简单的例子。
假设您的代码在处理订单之前需要有效输入。在过程方法中,您的代码(控制器)可能会触发'order'方法。然后订单验证然后提交或拒绝......
在发布者/订阅者方法中,您可能具有以下事件:OrderSubmitted,OrderValidated和OrderRejected。那些将是你的出版商。然后你有几个订阅者,ValidateOrder,CommitOrder和RejectOrder ...... ValidateOrder会订阅OrderSubmitted,CommitOrder订阅OrderValidated,最后RejectOrder订阅OrderRejected。
作为活动的参数,您可以传递订单。那系列事件将是......
您的控制器收到订单。代码假定事件为空检查...
void Init()
{
ValidateOrder += SomeValidateMethod;
CommitOrder += SomeCommitMethod;
RejectOrder += SomeRejectMethod;
}
void OrderReceived(Order o)
{
OrderEventArgs OEA = new OrderEventArgs(o);
ValidateOrder(this, OEA);
if (OEA.OrderIsValid)
CommitOrder(this, OEA);
else
RejectOrder(this, OEA);
}
就这样,我们有一些事件。现在,我们为什么要使用事件/代表?假设拒绝订单的代码更新了数据库,没问题。有人说,当订单被拒绝时,让我们给客户发邮件。你需要重构SomeRejectMethod吗?不,您只需创建一个新方法EmailOrderRejected,并将其作为订阅者添加到RejectOrder事件中。
这是一个非常小的例子,但在整个系统中使用事件代理时确实很有帮助。它有助于解耦方法之间的依赖关系......
我稍后会尝试跟进一些链接,祝你好运。
答案 8 :(得分:1)
我还没有看到的一件事是,委托使得在数据结构中存储方法变得容易。例如,我在功能要求中找到类似的东西并不罕见:
通过将其状态日期设置为活动日期,更新此费用的所有相关句子记录。如果收费与转移转介一起处理,则相关句子是句子类型为“DV”,“DCV”或“DVS”的句子。如果指控延期判决,则相关句子是句子类型为“DEJ”的句子。忽略所有其他指控和句子。
解决这个问题的一种方法是为句子构建一个类,为计费构建一个类,并从数据集中填充它们,然后将上述所有逻辑都粘贴到方法中。另一种方法是构建一组很好的嵌套条件。
第三种方式,更符合Steve McConnell的观察,即调试数据比编码更容易,是定义一个查找表,其中包含用于测试句子行的谓词:
private static readonly HashSet<string> DiversionTypes =
new HashSet() { "DV", "DCV", "DVS" };
private bool SentenceIsDiversion(DataRow r) { return (DiversionTypes.Contains(r.Field<string>("Type"))); }
private bool SentenceIsDEJ(DataRow r) { return r.Field<string>("Type") == "DEJ"; }
// Map charge disposition codes for diversion and DEJ to predicates that
// test sentence rows for relevance. Only sentences for charges whose disposition
// code is in this map and who are described by the related predicate should be
// updated.
private static readonly Dictionary<string, Func<DataRow, bool>> DispoToPredicateMap =
new Dictionary<string, Func<DataRow, bool>>
{
{ "411211", SentenceIsDiversion },
{ "411212", SentenceIsDiversion },
{ "411213", SentenceIsDEJ },
{ "411214", SentenceIsDEJ },
}
这使得更新逻辑看起来像这样:
string disposition = chargeRow.Field<string>("Disposition");
if (DispoToPredicateMap.ContainsKey(disposition))
{
foreach (DataRow sentenceRow in chargeRow.GetChildRows("FK_Sentence_Charge"))
{
if (DispoToPredicateMap[disposition](sentenceRow))
{
sentenceRow.SetField("StatusDate", eventDate);
}
}
}
在这三种方法中,这是最难提出的方法(或者,如果您不熟悉该技术,可以理解)。但编写覆盖100%代码的单元测试要容易得多,并且在触发条件发生变化时也很容易更新。