代表与事件

本文关键字:事件 | 更新日期: 2023-09-27 17:57:24

当一个类不能(或不应该)做某事时,事件或委托可能是一个解决方案。

class President
  Event AskedQuestion(QuestionEventArgs)
  Delegate GetAnswerToQuestion
class Scientist
  AnswerToQuestion()
// delegate approach
myPresident.GetAnswerToQuestion = AddressOf myScientist.AnswerToQuestion
// called once myPresident need it
// event approach
myScientist.AnswerToQuestion(questionEventArgs) Handles President.AskedQuestion
{ 
   // executed once president asked a question
}

在代表方法中,科学家方法由总统类直接使用,如果一位总统提出问题,科学家会用答案做出反应。

在.NET Framework代码中,我没有观察到直接使用委托。直接使用它是否错误,如果,为什么?

代表与事件

直接使用它是否错误,如果,为什么?

不,这没有错。

以下是我的想法。 委托字段之于事件,就像字符串字段之于属性一样。也就是说,您可能有:

class Car
{
    private string modelName;
    public string ModelName { get { return this.modelName; } }
    ...

逻辑上讲,型号名称是汽车的属性。当有人问你开什么样的车,你说"福特福克斯",你是在描述汽车的属性。你不认为"福特福克斯"是一个"领域"或一根"绳子",你认为它是一种汽车的名字。在计算机程序中,字符串字段只是名称存储方式的实现细节。该属性可以是字符串,也可以是枚举或其他任何内容;关键是,从逻辑上讲,汽车有型号名称,而不是字符串字段。

事件和委托的方式相同。汽车可以有一个"爆炸"事件(也许你正在编写一个视频游戏!),爆炸事件由委托类型的字段实现。爆炸是汽车在逻辑上做的事情;委托字段是实现事件的机制。

那么直接使用委托是"错误"的吗?不,当然不是。直接使用字符串是"错误的"。有时需要操作不是属性的字符串,有时需要操作不是事件的委托。

诀窍是编写代码,清楚地将机械流程与业务流程分开。如果您发现将大量字符串逻辑与属性逻辑混合在一起,或者将大量委托操作与事件混合在一起,则可以考虑尝试将机制代码与业务代码分开,以便更容易看到哪个是哪个。

框架中大量使用委托。LINQ 就是一个明显的例子:

var result = someCollection.Where(input => input.MatchesSomeCriteria);

Where采用具有特定签名的委托,调用该签名以确定是否在结果中包含项目。最常见的用法是 lamba 方法,如上所示,但您也可以传递一个方法:

string[] nums = new[]{ "1", "2", "3"};
int sum = nums.Select(int.Parse).Sum();

int.Parse匹配Select在这种情况下期望的必需委托签名(Func<string,int>),因此将为nums中的每个字符串调用它。

通常,当直接使用委托时,它们将作为将使用它们的方法调用的输入。虽然有些地方它们是消费者状态的一部分(例如HttpListener有一些委托类型的属性),但它们并不多。

原始环境中使用委托可能需要一些样板代码(定义委托、声明必要的成员变量和创建自定义保留封装的注册/注销方法等)。

撇开键入时间不谈,在原始数据中使用委托作为应用程序回调的另一个问题机制是这样一个事实,即如果您不将类的委托成员变量定义为私有,则调用方将可以直接访问委托对象。如果是这种情况,调用方将能够将变量重新分配给新的委托对象(有效地删除当前函数列表call),更糟糕的是,调用方将能够直接调用委托的调用列表。

事件

事件实际上是我真正喜欢 .NET 的原因之一,因为它允许您声明一个更干净的接口。 你可以有一个总统类,宣布它需要一个答案,而不将其绑定到应答代理的实现,如

interface IPresident
{
     event Action<QuestionArgs, IPresident> HasQuestion;
     void RecieveAnswer(QuestionArgs,Answer);
}

然后在你的科学家课上

partial class Scientist
{
     public Scientist(IPresident president)
     {
          president.HasQuestion += TryToAnswerQuestion;
     }
     private void TryToAnswerQuestion(QuestionArgs question, IPresident asker)
     {
         if(CanAnswerQuestion(question))
         {
             asker.RecieveAnswer(question,GetAnswer(question));
         }
     }
}

如果一个新班级想回答总统的问题,他们需要做的就是倾听事件,表明有一个问题需要回答,然后在他们有能力的情况下回答它。 如果科学家想回答其他人的问题,我们只需要实现一个附加到他们的事件的方法。

直接委托调用

上面概述的委托方法的问题在于它破坏了封装。 它将科学家和总裁的实现紧密结合,并使代码变得脆弱。 当您有其他人回答问题时会发生什么?在您的示例中,您将需要修改您的 Scientist 实现以添加新功能,这被称为"脆"代码,是一件坏事。 这种技术确实在构图中有一些作用,但它很少(如果有的话)是最好的选择。

LINQ 的情况有所不同,因为您不会将委托公开为类/接口的成员。 相反,您将它用作调用方声明的函子,以让您知道调用方感兴趣的信息。 由于您正在进行"往返"封装,因此保持不变。这使您可以定义非常干净和强大的 API。

我们可以以科学家为例,并使用这种技术对其进行扩展,以允许某人找出我们可以像这样回答的问题。

 partial class Scientist
 {
     public IEnumerable<QuestionArgs> FindQuestions(Predicate<QuestionArgs> interest, IPresident asker)
     {
         return this.Questions.Where( x => interest(x) == true && x.IsAuthorizedToAsk(asker))
     }
 }
 // ...
partial class President
{
    FirePhysicists()
    {
        foreach(var scientist in scientists)
        {
             if(scientist.FindQuestions(x => x.Catagory == QuestionCatagory.Physics, this).Count != 0)
             {
                 scientist.Fire();
             }
         }
      }
 }

请注意,FindQuestions 方法让我们不必实现一堆其他代码来询问我们在没有传递委托的情况下需要的科学家。虽然这不是您找到直接调用的委托的唯一情况,但它是最常见的情况之一