C# 延迟事件处理,直到触发事件

C# 延迟事件处理,直到触发事件,c#,events,event-handling,C#,Events,Event Handling,在C#中,在实体被完全修改之前,延迟处理所有已知事件的最佳方式是什么? 例如,假设一个实体MyEntity具有属性ID、名称和描述 public class MyEntity { public Int32 ID { get; set; } public String Name { get; set; } public String Description { get; set; } } 修改这些属性时,会为每次修改触发一个事件 有时,

在C#中,在实体被完全修改之前,延迟处理所有已知事件的最佳方式是什么? 例如,假设一个实体MyEntity具有属性ID、名称和描述

   public class MyEntity
   {
       public Int32 ID { get; set; }
       public String Name { get; set; }
       public String Description { get; set; }
   }
修改这些属性时,会为每次修改触发一个事件

有时,ID是唯一修改的属性,有时修改所有属性。我希望修改事件的注册侦听器等待“批处理”中所有被修改的属性都被修改

实现这一目标的最佳方式是什么

在我看来,类似于UnitOfWork模式的东西,可以在调用堆栈的顶层围绕方法调用包装一个using语句,但不知道如何实现这样的事情

编辑: 作为澄清。。。侦听器分布在应用程序中,并在其他线程中执行。例如,另一个参与者设置它必须调用MyEntity.name属性才能设置值的名称


由于设计原因,对Name属性的修改可能会触发其他属性的更改,因此监听器需要知道属性的修改已经完成。

只有执行修改的代码才能知道其一批更改何时完成

我对类似类所做的是提供
SuspendNotifications()
resumennotifications()
方法,这些方法以明显的方式调用(即在进行一系列更改之前调用suspend,完成后调用resume)

它们在内部维护一个计数器,该计数器在SuspendNotifications()中递增,在ResumeNotifications()中递减,如果递减结果为零,则发出通知。我这样做是因为有时我会修改一些属性,然后调用另一个方法,该方法会修改更多属性,而该方法本身会调用suspend/resume

(如果调用resume的次数太多,我抛出了一个异常。)

如果更改了多个属性,则最终通知不会命名要更改的属性(因为有多个属性)。我想您可以累积一个已更改属性的列表,并将其作为通知的一部分发布,但这听起来不是很有用

还请注意,线程安全可能是您的问题,也可能不是。您可能需要使用锁定和/或
Interlocked.Increment()

另一件事是,如果出现异常,您当然需要尝试/捕获您的呼叫以挂起/恢复。您可以通过编写一个实现IDisposable并在其Dispose中调用resume的包装器类来避免这种情况

代码可能如下所示:

public void DoStuff()
{
    try
    {
        _entity.SuspendNotifications();
        setProperties();
    }

    finally
    {
        _entity.ResumeNotifications();
    }
}

private setProperties()
{
    _entity.ID = 123454;
    _entity.Name = "Name";
    _entity.Description = "Desc";
}

[编辑]

如果要引入一个接口,比如说
isuspendablennotifications
,那么可以编写一个
IDisposable
包装类来简化事情

下面的例子说明了这个概念;使用
NotificationSuspender
简化了(实际上删除了)try/catch逻辑

请注意,
类实体
当然不会实际实现挂起/恢复或提供任何错误处理;这是留给读者的谚语练习。:)


我认为这将是困难的,因为事件是异步触发的,但由执行线程同步处理。一种可能是使用
AutoResetEvent
ManualResetEvent
并使用
WaitOne
-方法等待
Set
释放它。
您可能需要将其与
互斥体结合使用。但是,如果您只在一个线程上工作,这将不起作用


有关
手动重置事件
自动重置事件

的信息,请参见假设所有事件使用相同的签名:

  • 在MyEntity实例化时初始化一个委托,例如
    eventQueue
    ,并初始化一个int值,例如'queueRequiredLength'
  • 每个属性设置器将其事件添加到队列中(如果尚未存在),
    eventQueue+=newEvent而不是仅仅触发事件
  • 然后,每个属性设置器检查队列的长度,并在(长度==queueRequiredLength){eventQueue();}
  • (我不知道如何检查委托中“排队”的方法的数量,但在最坏的情况下,您也可以保留一个计数器,并在每次添加到队列中时递增它)。

    我可以建议

    public class MyEntity
    {
        private const int FieldsCount = 3;
    
        private Int32 id;
        private String name;
        private String description;
    
        private HashSet<string> dirty = new HashSet<string>();
    
        public Int32 ID
        {
            get { return id; }
            set
            {
                id = value;
                dirty.Add("id");
                GoListeners();
            }
        }
    
        //...
    
        private void GoListeners()
        {
            if (dirty.Count == FieldsCount)
            {
                //...
                dirty.Clear();
            }
        }
    
    }
    
    公共类MyEntity
    {
    私家侦探int FIELDSCONT=3;
    私有Int32 id;
    私有字符串名称;
    私有字符串描述;
    private HashSet dirty=new HashSet();
    公共Int32 ID
    {
    获取{return id;}
    设置
    {
    id=值;
    脏。添加(“id”);
    GoListeners();
    }
    }
    //...
    私有侦听器()
    {
    if(dirty.Count==fieldscont)
    {
    //...
    脏的,干净的;
    }
    }
    }
    
    在很大程度上取决于其他要求,例如,侦听器是否对单个属性更改采取行动,还是始终处理实体,而不管修改的数量和位置如何?为什么不使用标志?如果设置了标志1和2,但未设置标志3。。。什么都不要做。当事件触发并设置标志3时,然后处理您想要执行的任何代码。OT:我意识到我最初使用“using”语句的想法源自log4net实现,其中log4net.NDC.Push()调用的目的是将上下文消息推送到当前线程的上下文…这似乎是我最初对using语句的想法。我想我可以将事件添加到内部队列,然后在恢复通知时处理队列。如果是这样的话-我需要队列中的一些东西来让侦听器知道批处理何时完成…此模式的一个很好的MSDN示例是使用它的控件,这是
    public class MyEntity
    {
        private const int FieldsCount = 3;
    
        private Int32 id;
        private String name;
        private String description;
    
        private HashSet<string> dirty = new HashSet<string>();
    
        public Int32 ID
        {
            get { return id; }
            set
            {
                id = value;
                dirty.Add("id");
                GoListeners();
            }
        }
    
        //...
    
        private void GoListeners()
        {
            if (dirty.Count == FieldsCount)
            {
                //...
                dirty.Clear();
            }
        }
    
    }