Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delay event handling until events have been fired

In C#, what's the best way to delay handling of all known events until an entity has been fully modified? Say, for example that an entity - MyEntity - has the properties ID, Name and Description...

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

When modifying each of these properties, an event is fired for each modification.

Sometimes, the ID is the only property modified and sometimes all properties are modified. I want the registered listeners of the modification event to wait until all properties being modified in the "batch" have been modified.

What is the best way to accomplish this?

In my head, something similar to the UnitOfWork-pattern, where it is possible to wrap a using statement around the method call in the top level of the call stack but have no clue how to implement such a thing...

Edit: As a clarification... Listeners are spread out through the application and are executing in other threads. Another actor sets - for example - the name it must call the MyEntity.Name property to set the value.

Due to the design, the modification of the Name property can trigger other properties to change, thus the need for listeners to know that the modification of properties have been completed.

like image 718
ForestC Avatar asked Mar 13 '13 13:03

ForestC


1 Answers

Only the code performing the modifications can know when its batch of changes is complete.

What I did with my similar classes is to provide SuspendNotifications() and ResumeNotifications() methods, which are called in the obvious way (i.e. call suspend before making a bunch of changes, call resume when done).

They internally maintain a counter which is incremented in SuspendNotifications() and decremented by ResumeNotifications(), and if the decrement results in zero, a notification is issued. I did it this way because sometimes I would modify some properties and then call another method which modified some more, and which itself would call suspend/resume.

(If resume is called too many times, I thrown an exception.)

If multiple properties are changed, the final notification doesn't name the property being changed (since there is more than one). I suppose you could accumulate a list of changed properties and issue that as part of the notification, but that doesn't sound very useful.

Also note that thread safety may or may not be an issue for you. You might need to use locking and/or Interlocked.Increment() etc.

The other thing is that of course you end up needing try/catch around your calls to suspend/resume in case there's an exception. You can avoid that by writing a wrapper class that implements IDisposable and calls resume in its Dispose.

Code might look like this:

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

    finally
    {
        _entity.ResumeNotifications();
    }
}

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

[EDIT]

If you were to introduce an interface, say ISuspendableNotifications, you could write an IDisposable wrapper class to simplify things.

The example below illustrates the concept; The use of NotificationSuspender simplifies (in fact removes) the try/catch logic.

Note that class Entity of course does not actually implement suspend/resume or provide any error handling; that's left as the proverbial exercise for the reader. :)

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";
            }
        }
    }
}
like image 134
Matthew Watson Avatar answered Oct 12 '22 22:10

Matthew Watson