在C#中,在实体完全修改之前,延迟处理所有已知事件的最佳方法是什么? 比如说,实体 - MyEntity - 具有属性ID,名称和描述......
public class MyEntity
{
public Int32 ID { get; set; }
public String Name { get; set; }
public String Description { get; set; }
}
修改每个属性时,会为每次修改触发一个事件。
有时,ID是唯一已修改的属性,有时会修改所有属性。我希望修改事件的已注册侦听器等到“批处理”中修改的所有属性都被修改。
实现这一目标的最佳方法是什么?
在我的脑海中,类似于UnitOfWork模式,可以在调用堆栈的顶层包装一个using语句,但不知道如何实现这样的事情... < / p>
编辑: 作为澄清......监听器通过应用程序分散,并在其他线程中执行。另一个actor设置 - 例如 - 它必须调用MyEntity.Name属性来设置值的名称。
由于设计原因,对Name属性的修改可以触发其他属性更改,因此需要听众知道属性的修改已经完成。
答案 0 :(得分:4)
只有执行修改的代码才能知道其批量更改何时完成。
我对类似类的做法是提供SuspendNotifications()
和ResumeNotifications()
方法,这些方法以明显的方式调用(即在进行一系列更改之前调用挂起,完成后调用resume)。
它们在内部维护一个计数器,该计数器在SuspendNotifications()中递增并由ResumeNotifications()递减,如果递减结果为零,则发出通知。我是这样做的,因为有时我会修改一些属性,然后调用另一个修改了更多的方法,它本身会调用suspend / 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";
}
[编辑]
如果您要介绍一个接口,比如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";
}
}
}
}
答案 1 :(得分: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();
}
}
}
答案 2 :(得分:0)
我认为这很难,因为事件是异步触发的,但是由执行线程同步处理。一种可能性是使用AutoResetEvent
或ManualResetEvent
并使用WaitOne
- 方法等待Set
释放它。
您可能需要将其与Mutex
结合使用。但是如果你只是在一个线程上工作,这将无法工作。
答案 3 :(得分:0)
假设您的所有活动都使用相同的签名:
eventQueue
,以及一个int值,例如'queueRequiredLength'eventQueue += newEvent;
而不是仅仅触发事件。if(length == queueRequiredLength) {eventQueue();}
(我不知道如何检查代理中“排队”的方法数量,但最糟糕的情况是你也可以保留一个计数器,并在每次添加到队列时增加它。) / p>