FREE BOOK

Chapter 6: Improving Class Quality

Posted by Packt Publishing Free Book | Visual Studio 2010 September 15, 2010
In this chapter we will see how to refactor our code to be more cohesive and less coupled.

Events

Although events are effectively callbacks, they have first-class status in C# and .NET with their own syntax and follow a specific protocol. Events are an optional one-way communication between one class and many subscribers. If you're refactoring from a one-to-one coupling with another class and the use of that other class is to effectively notify it of particular values and not receive any information in return, events are a very apt refactoring.

Events are different from the average use of delegates in one important way: multicasting. This means the standard event interface automatically supports combining event listeners into a multicast delegate under the covers. You can certainly support multicast delegates in our delegate example, but we'd have to expand the interface to support "adding" and "removing" a callback. For example:

/// <summary>
/// Invoice class that supports
/// multicast delegates
/// </summary>
public class Invoice
{
    private Func<float, float, float> calculateGrandTotalCallback;
    //...
    public void AddCalculateGrandTotalCallback(Func<float, float, float> callback)
    {
        calculateGrandTotalCallback += callback;
    }
    public void RemoveCalculateGrandTotalCallback(Func<float, float, float> callback)
    {
        calculateGrandTotalCallback -= callback;
    }
}

But, of course, this isn't what callbacks are intended for and thus not applicable for our exemplified purpose. We only get one result from executing multiple delegates with a multicast delegate. For the same reason, multicast delegates aren't suitable for delegates that return values; events that return values are not appropriate. Let's return to a disposable Invoice for a moment. Let's say we wanted to inform a subscriber when we are disposed. One way of doing that is with delegates. For example:

/// <summary>
/// Invoice that implements IDisposable
/// </summary>
public class Invoice : IDisposable
{
    private bool disposed = false;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    private void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                //...
            }
            Action callback = disposedCallback;
            if (callback != null)
            {
                callback();
            }
            disposed = true;
        }
    }
    private List<InvoiceLineItem> invoiceLineItems;
    private Action disposedCallback;
    public Invoice(IEnumerable<InvoiceLineItem> invoiceLineItems, Action disposedCallback)
    {
        this.invoiceLineItems = new List<InvoiceLineItem>(invoiceLineItems);
        this.disposedCallback = disposedCallback;
    }
    //...
}

This does what we want, but it limits us to just one subscriber-the subscriber that created the Invoice object. Clearly this isn't the best way to do this, plus it's uncommon and not intuitive. Using events is much more intuitive and supports multiple subscribers. Refactoring to events involves changing the delegate to a public event, removing the initialization of the delegate in the constructor, changing the invocation of the Invoice constructor, changing the method used for the delegate to include an object and an EventArgs parameter, using this new method to assign an event to the Invoice object, and changing the Dispose method to use an EventHandler object instead. This results in something like the following:

/// <summary>
/// Disposable Invoice and Disposed event
/// </summary>
public class Invoice : IDisposable
{
    private bool disposed = false;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    private void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                //...
            }
            EventHandler handler = Disposed;
            if (handler != null)
            {
                 handler(this, EventArgs.Empty);
            }
            disposed = true;
        }
    }
    private List<InvoiceLineItem> invoiceLineItems;
    public EventHandler Disposed;
    public Invoice(IEnumerable<InvoiceLineItem> invoiceLineItems)
    {
        this.invoiceLineItems = new List<InvoiceLineItem>(invoiceLineItems);
    }
    //...
}

Total Pages : 17 1314151617

comments