2013-03-13 71 views
12

在C#中,延迟处理所有已知事件的最佳方式是什么,直到一个实体被完全修改为止? 说,例如,一个实体 - myEntity所 - 具有属性的ID,名称和描述...延迟事件处理,直到事件被触发为止

public class MyEntity 
    { 
     public Int32 ID { get; set; } 
     public String Name { get; set; } 
     public String Description { get; set; } 
    } 

当修改每个这些属性的,一个事件被触发为每个修改。

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

完成此操作的最佳方法是什么?

在我的脑海中,类似于UnitOfWork-pattern的东西,可以在调用堆栈顶层的方法调用中包装using语句,但不知道如何实现这样的事情......

编辑: 作为一个澄清......听众分散在整个应用程序,并在其他线程执行。另一个actor(例如)设置它必须调用MyEntity.Name属性来设置值的名称。

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

+0

很大程度上取决于其他要求,例如听众对单个财产的变化采取行动还是他们总是处理实体,而不管修改的数量和位置如何? – 2013-03-13 13:34:06

+0

为什么不使用标志?如果标志1和2被设置,但3不是...不要做任何事情。当事件触发并设置了标记3时,THEN会处理您要执行的任何代码。 – 2013-03-13 13:36:25

+0

OT: 我意识到我最初的想法与“使用”语句来自log4net实现,其中调用log4net.NDC.Push()以将上下文消息推送到当前线程的上下文中... – ForestC 2013-03-13 15:02:57

回答

4

只有执行修改的代码才能知道其批次更改是否完成。

我对我的类似课程所做的工作是提供SuspendNotifications()ResumeNotifications()方法,这些方法以明显的方式调用(即在进行一系列更改前调用暂停,完成时调用恢复)。

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

(如果简历被称为太多次,我抛出的异常。)

如果多个属性被更改,最后通知不名要更改的属性(因为有不止一个)。我想你可以累积一个已更改的属性列表,并将其作为通知的一部分发布,但这听起来不太有用。

另请注意,线程安全性可能会或可能不会成为您的问题。您可能需要使用锁定和/或Interlocked.Increment()等。

另一件事是,当然最终需要围绕您的调用try/catch暂停/恢复,以防出现异常。您可以通过编写实现IDisposable的包装类来避免这种情况,并在其Dispose中调用resume。

代码可能是这样的:

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

    finally 
    { 
     _entity.ResumeNotifications(); 
    } 
} 

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

[编辑]

如果你要引入一个接口,说ISuspendableNotifications,你可以写一个IDisposable包装类把事情简单化。

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

请注意,class Entity当然并不实际挂起/恢复或提供任何错误处理;这对读者来说就是众所周知的。 :)

using System; 

namespace Demo 
{ 
    public interface ISuspendableNotifications 
    { 
     void SuspendNotifications(); 
     void ResumeNotifications(); 
    } 

    public sealed class NotificationSuspender: IDisposable 
    { 
     public NotificationSuspender(ISuspendableNotifications suspendableNotifications) 
     { 
      _suspendableNotifications = suspendableNotifications; 
      _suspendableNotifications.SuspendNotifications(); 
     } 

     public void Dispose() 
     { 
      _suspendableNotifications.ResumeNotifications(); 
     } 

     private readonly ISuspendableNotifications _suspendableNotifications; 
    } 

    public sealed class Entity: ISuspendableNotifications 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 
     public string Description { get; set; } 

     public void SuspendNotifications() {} 
     public void ResumeNotifications() {} 
    } 

    public static class Program 
    { 
     public static void Main(string[] args) 
     { 
      Entity entity = new Entity(); 

      using (new NotificationSuspender(entity)) 
      { 
       entity.Id = 123454; 
       entity.Name = "Name"; 
       entity.Description = "Desc"; 
      } 
     } 
    } 
} 
+0

+1我的首选方法。 – 2013-03-13 13:51:44

+0

这看起来有点像我对使用语句的初步想法。我想我可以将事件添加到内部队列,然后在通知恢复时处理队列。 如果是这样 - 我需要队列中的某些东西让听众知道批次何时完成... – ForestC 2013-03-13 14:04:54

+2

这个模式的一个很好的MSDN示例是使用它的[SuspendLayout](http://msdn.microsoft.com/zh-cn/library/system.windows.forms.control.suspendlayout.aspx)和[ResumeLayout]( http://msdn.microsoft.com/en-us/library/system.windows.forms.control.resumelayout.aspx) – 2013-03-13 14:31:24

0

我认为这将是艰难的,因为事件是异步触发的,但是由执行线程同步处理。一种可能性是使用AutoResetEventManualResetEvent并使用WaitOne-方法来等待Set发布它。
您可能需要将它与Mutex结合使用。但是如果你只在一个线程上工作,这将不起作用。

请参阅here对于ManualResetEventhere对于AutoResetEvent

+0

该应用程序是多线程的。也许最好的选择是“收集”一个父事件(子事件是儿童)中的所有事件? – ForestC 2013-03-13 14:07:57

+0

@Forest我不太了解你的程序的结构。如果你能够结合它,它可能会工作。 – 2013-03-13 14:11:03

0

假设所有的事件都使用相同的签名:

  1. 在myEntity所实例初始化一个委托例如eventQueue,和一个int值,例如,“queueRequiredLength”
  2. 每个属性的setter增加了增加了他们的事件队列如果不是已经存在,eventQueue += newEvent;,而不是仅仅发射事件。
  3. 每个属性的setter然后检查队列的长度并触发委托(即所有排队的事件)if(length == queueRequiredLength) {eventQueue();}

(关我的头顶,我不知道如何检查的方法数“排队“,但最坏的情况下,你也可以保留一个计数器,每增加一个计数器就增加一个计数器)。

1

我可以建议

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(); 
     } 
    } 

}