12

在 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属性的修改会触发其他属性的变化,因此需要监听者知道属性的修改已经完成。

4

4 回答 4

4

只有执行修改的代码才能知道它的批量更改何时完成。

我对类似的类所做的是提供SuspendNotifications()ResumeNotifications()方法,它们以明显的方式调用(即在进行大量更改之前调用暂停,完成后调用恢复)。

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

(如果 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";
            }
        }
    }
}
于 2013-03-13T13:27:53.710 回答
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();
        }
    }

}
于 2013-03-13T13:35:29.830 回答
0

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

请参阅此处ManualResetEvent此处。_AutoResetEvent

于 2013-03-13T13:28:49.910 回答
0

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

  1. 初始化一个委托,例如eventQueue在 MyEntity 实例化和一个 int 值,例如“queueRequiredLength”
  2. 每个属性设置器都将其事件添加到队列中(如果尚不存在),eventQueue += newEvent;而不是仅仅触发事件。
  3. 然后每个属性设置器检查队列的长度并触发委托(即所有排队的事件)if(length == queueRequiredLength) {eventQueue();}

(我不知道如何检查委托中“排队”的方法数量,但最坏的情况是你也可以只保留一个计数器并在每次添加到队列时增加它)。

于 2013-03-13T13:29:45.013 回答