Exceptions During EventHandler Invocation and Allowing No Subscribers

Ever create a middle-tier library and have a ton of EventHandlers / EventArgs on your classes because you think it will be convenient in some use cases that just aren't needed up front?

In my experience, this always winds up as an annoyance on invocation as the end user of the library would need to wire up all those events even if they weren't being used, or risk a NullReferenceException saying "Object reference not set to an instance of an object".

This post shows a quick and easy pattern to use across any application or library to make consuming events completely optional. You can tell your classes "if no one is listening, don't invoke".

Lets start out with a simple console application and a few classes. We have a forest with some trees:

public class Forest
{
 public int TreeCount { get; private set; }
 public Forest(int numberOfTrees)
 {
  TreeCount = numberOfTrees;
 }

 public void RemoveATree()
 {
  TreeCount--;
 }

 public bool HasTrees { get { return TreeCount > 0; } }
}
And a Chopper that knows how to cut down trees in that forest.
public class TreeChopper
{
    private readonly Forest _forest;
    public TreeChopper(Forest aForest)
    {
        _forest = aForest;
    }

    public void ChopATreeDown()
    {
        if (!_forest.HasTrees)
        {
            throw new NoTreesLeftException("There are no trees in the forest to chop down.");
        }

        OnTreeChopping.Invoke(this, new TreeChoppingEventArgs("Starting to chop a tree."));
        OnTreeChopped.Invoke(this, new TreeChoppingEventArgs("Finished chopping."));
        OnTreeFalling.Invoke(this, new TreeChoppingEventArgs("Tree is starting to fall down."));
        _forest.RemoveATree();
        OnTreeFell.Invoke(this, new TreeChoppingEventArgs("Tree has fallen. There are "+_forest.TreeCount+" trees left in the forest."));
    }

    public EventHandler<TreeChoppingEventArgs> OnTreeChopping;
    public EventHandler<TreeChoppingEventArgs> OnTreeChopped;
    public EventHandler<TreeChoppingEventArgs> OnTreeFalling;
    public EventHandler<TreeChoppingEventArgs> OnTreeFell;

}

Note that there's 4 events for stages of chopping down that tree, and a simple message EventArgs:

public class TreeChoppingEventArgs : EventArgs
{
    public string Message { get; set; }
    public TreeChoppingEventArgs(string message)
    {
        Message = message;
    }
}

public class NoTreesLeftException : ArgumentOutOfRangeException
{
    public NoTreesLeftException(string message) : base(message){}
}

This is all well and good, and if we set up a runner, we can chop down all the trees in a little console app. The chopping:

public static void Run()
{
    //1. set up a forest.
    var forest = new Forest(numberOfTrees:5);

    //2. wire up all events.
    var chopper = new TreeChopper(forest);

    chopper.OnTreeChopping += OnTreeChoppingEvent;
    chopper.OnTreeChopped += OnTreeChoppingEvent;
    chopper.OnTreeFalling += OnTreeChoppingEvent;
    chopper.OnTreeFell += OnTreeChoppingEvent;


    //3. cut down all trees in the forest
    while (forest.HasTrees)
    {
        chopper.ChopATreeDown();
    }
}

private static void OnTreeChoppingEvent(object sender, TreeChoppingEventArgs e)
{
    Console.WriteLine(e.Message);
}

So this displays the following output:

Starting to chop a tree.
Finished chopping.
Tree is starting to fall down.
Tree has fallen. There are 4 trees left in the forest.
Starting to chop a tree.
Finished chopping.
Tree is starting to fall down.
Tree has fallen. There are 3 trees left in the forest.
Starting to chop a tree.
Finished chopping.
Tree is starting to fall down.
Tree has fallen. There are 2 trees left in the forest.
Starting to chop a tree.
Finished chopping.
Tree is starting to fall down.
Tree has fallen. There are 1 trees left in the forest.
Starting to chop a tree.
Finished chopping.
Tree is starting to fall down.
Tree has fallen. There are 0 trees left in the forest.


But that's pretty verbose. Lets say from the outside perspective, we don't care when the tree chopping started... or even finished. The only thing we care about is when that tree fell, so we can drag it away and use the wood. How do we invoke all of the methods, in case they are used, but get protected in case they are not listened to?

The answer lies in how events work. An event handler is simply a way of keeping track of how to call the delegates -- the places that the event truly gets 'handled'.

Let's take a look at the event handler's definition (.NET 4.0 EventHandler<TEventArgs> where TEventArgs:EventArgs )

This is essentially a subscription list. If you've wired up a handler for the event, it gets added to the list (really an Array). If not, the list is empty.

So to make an event that ensures it's only fired when there's someone listening the assumption is that we simply have to do the following:

Before:

OnTreeChopping.Invoke(this, new TreeChoppingEventArgs("Starting to chop a tree."));

After:

if (OnTreeChopping.GetInvocationList() != null)
{
OnTreeChopping.Invoke(this, new TreeChoppingEventArgs("Starting to chop a tree."));
}

Yet in practice, you'll find that this still throws the NullReferenceException. Why? Well, the OnTreeChopping object (the EventHandler itself) turns out to be null. So the first reaction is just to do the following. Check to see if the EventHandler is null, then invoke it if it's not.

Before:

OnTreeChopping.Invoke(this, new TreeChoppingEventArgs("Starting to chop a tree."));

After:

if (OnTreeChopping != null)
{
OnTreeChopping.Invoke(this, new TreeChoppingEventArgs("Starting to chop a tree."));
}

So now, if we wrap all of our events in this little block, they'll be 'safe', right? Well, lets assume the answer to that is true for the moment. Writing the same thing over and over again would be smelly; we've just made it 4 lines of code (well, 2 + braces) every time we want to invoke an event. Enter a handy extension method:

public static class EventHandlerExtensions
{
    public static void TryInvoke<T>
        (this EventHandler<T> anEvent, object sender, T eventArgs)
        where T:EventArgs
    {
        if (anEvent != null)
        {
            anEvent.Invoke(sender, eventArgs);
        }
    }    
}

So in our calling code:

Before:

OnTreeChopping.Invoke(this, new TreeChoppingEventArgs("Starting to chop a tree."));

After:

OnTreeChopping.TryInvoke(this, new TreeChoppingEventArgs("Starting to chop a tree."));

And if we only care about the tree being done falling, we can comment out the wiring for the other events as so:

//chopper.OnTreeChopping += OnTreeChoppingEvent;
//chopper.OnTreeChopped += OnTreeChoppingEvent;
//chopper.OnTreeFalling += OnTreeChoppingEvent;
chopper.OnTreeFell += OnTreeChoppingEvent;

The invocation as so:

OnTreeChopping.TryInvoke(this, new TreeChoppingEventArgs("Starting to chop a tree."));
OnTreeChopped.TryInvoke(this, new TreeChoppingEventArgs("Finished chopping."));
OnTreeFalling.TryInvoke(this, new TreeChoppingEventArgs("Tree is starting to fall down."));
_forest.RemoveATree();
OnTreeFell.TryInvoke(this, new TreeChoppingEventArgs("Tree has fallen. There are " + _forest.TreeCount + " trees left in the forest."));

And our output looks like so:
Tree has fallen. There are 4 trees left in the forest.
Tree has fallen. There are 3 trees left in the forest.
Tree has fallen. There are 2 trees left in the forest.
Tree has fallen. There are 1 trees left in the forest.
Tree has fallen. There are 0 trees left in the forest.

 

It's all nice and easy; no messages about chopping, which I don't care about. If the event never gets wired, it doesn't get called. Great. But that begs the question of why to even provide GetInvocationList anyway. Is it useful to us at all? Well, it turns out that there are cases where a misbehaving (read: exception throwing) event handler can spoil invocation for the rest of the handlers.


Chris Tacke had a great writeup on this here (http://blog.opennetcf.com/ctacke/2010/05/27/WhyYouShouldUseEventHandlerGetInvocationList.aspx), and I'm going to apply this scenario to our example. Lets modify our code somewhat to create something bad happening. Lets say we have two event handlers. One transmits the message about the tree having fallen to an RSS feed, and another logs it to some sql database. The sql database server just went down, but we still want to publish the live events to external consumers depending on the data for estimating how much wood they could have available for their production line. First of all, our extension method is now going to swallow the Exceptions, and write them nicely to our console output:

public static void TryInvoke<T>(this EventHandler<T> anEvent, object sender, T eventArgs)
    where T:EventArgs
{
    if (anEvent != null)
    {
        try
        {
            anEvent.Invoke(sender, eventArgs);    
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    
        
    }
}
Second, we're going to create a 'good' event handler, and a 'bad' one:
private static void OnTreeChoppingEventLogToSql(object sender, TreeChoppingEventArgs e)
{
    Console.WriteLine(e.Message);
    throw new InvalidDataException("sql database does not exist");
    Console.WriteLine("Logged to sql.");
}

private static void OnTreeChoppingEventLogToRssFeed(object sender, TreeChoppingEventArgs e)
{
    Console.WriteLine(e.Message);
    Console.WriteLine("Logged to RSS feed.");
}

And when we wire them up:

chopper.OnTreeFell += OnTreeChoppingEventLogToSql;
chopper.OnTreeFell += OnTreeChoppingEventLogToRssFeed;

So what does the output look like?
Tree has fallen. There are 4 trees left in the forest.
sql database does not exist
Tree has fallen. There are 3 trees left in the forest.
sql database does not exist
Tree has fallen. There are 2 trees left in the forest.
sql database does not exist
Tree has fallen. There are 1 trees left in the forest.
sql database does not exist
Tree has fallen. There are 0 trees left in the forest.
sql database does not exist

Wait... what happened there? What about that critical RSS feed? Well, it turns out that the exception thrown blocked further invocation of other event handlers. Keep in mind that event handlers are invoked in the order that they are added to the invocation list. Also, an exception by default prevents further invocation. So if we want to change that behavior, we'll need to manually call each EventHandler<T>, rather than just let the MulticastDelegate handle it.

So our extension method changes to:

public static void TryInvoke<T>(this EventHandler<T> anEvent, object sender, T eventArgs)
    where T:EventArgs
{
    if (anEvent != null)
    {
        foreach (EventHandler<T> wiredHandler in anEvent.GetInvocationList())
        {
            try
            {
                wiredHandler(sender, eventArgs);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}

Which makes our output work:
Tree has fallen. There are 4 trees left in the forest.
sql database does not exist
Tree has fallen. There are 4 trees left in the forest.
Logged to RSS feed.
Tree has fallen. There are 3 trees left in the forest.
sql database does not exist
Tree has fallen. There are 3 trees left in the forest.
Logged to RSS feed.
Tree has fallen. There are 2 trees left in the forest.
sql database does not exist
Tree has fallen. There are 2 trees left in the forest.
Logged to RSS feed.
Tree has fallen. There are 1 trees left in the forest.
sql database does not exist
Tree has fallen. There are 1 trees left in the forest.
Logged to RSS feed.
Tree has fallen. There are 0 trees left in the forest.
sql database does not exist
Tree has fallen. There are 0 trees left in the forest.
Logged to RSS feed.

Note that this won't really work as we want it to if we need to get our exceptions properly bubbled out after the TryInvoke call. To this end, we can introduce some Exception collecting,

including a wrapper for event errors:

public class EventInvocationException : Exception
{
    public List<Exception> Exceptions { get; set; }
    public string Message { get; set; }
    public EventInvocationException(string message, List<Exception> exceptions)
    {
        Message = message;
        Exceptions = exceptions;
    }
}

And then collect them as they are thrown:

public static void TryInvoke<T>(this EventHandler<T> anEvent, object sender, T eventArgs)
    where T:EventArgs
{
    var bubbledExceptions = new List<Exception>();
    if (anEvent != null)
    {
        foreach (EventHandler<T> wiredHandler in anEvent.GetInvocationList())
        {
            try
            {
                wiredHandler(sender, eventArgs);
            }
            catch (Exception e)
            {
                bubbledExceptions.Add(e);
            }
        }
    }

    if (bubbledExceptions.Count>0)
    {
        throw new EventInvocationException("At least one event handler threw an exception", bubbledExceptions);
    }
}

Now since this will be used in a high-level library, allow the invoker to specify allowed behavior, with a default of allowing all of the exceptions to bubble out:

public static class EventArgsExtensions
{
    public static void TryInvoke<T>(this EventHandler<T> anEvent, object sender, T eventArgs, bool allowMultipleExceptions=true)
        where T:EventArgs
    {
        if (allowMultipleExceptions)
        {
            anEvent.TryInvokeWithMultipleExceptions(sender, eventArgs);
        }
        else
        {
            anEvent.TryInvokeWithOnlyOneExceptionAllowed(sender, eventArgs);
        }
    }

    private static void TryInvokeWithOnlyOneExceptionAllowed<T>(this EventHandler<T> anEvent, object sender, T eventArgs)
        where T:EventArgs
    {
        if (anEvent!=null)
        {
            anEvent.Invoke(sender, eventArgs);
        }
    }

    private static void TryInvokeWithMultipleExceptions<T>(this EventHandler<T> anEvent, object sender, T eventArgs)
        where T:EventArgs
    {
        var bubbledExceptions = new List<Exception>();
        if (anEvent != null)
        {
            foreach (EventHandler<T> wiredHandler in anEvent.GetInvocationList())
            {
                try
                {
                    wiredHandler(sender, eventArgs);
                }
                catch (Exception e)
                {
                    bubbledExceptions.Add(e);
                }
            }
        }

        if (bubbledExceptions.Count>0)
        {
            throw new EventInvocationException("At least one event handler threw an exception", bubbledExceptions);
        }
    }
}

Now we can still call the TryInvoke on our event like before:

OnTreeFell.TryInvoke(this, new TreeChoppingEventArgs("Tree has fallen. There are " + _forest.TreeCount + " trees left in the forest."));

However if we want the first exception to really blow up, we can use the old-style plain invoke:

var allowMultipleExceptions=false;
OnTreeFell.TryInvoke(
this, 
new TreeChoppingEventArgs("Tree has fallen. There are " +         _forest.TreeCount + 
    " trees left in the forest."), 
allowMultipleExceptions);

And the best part is that all you have to do from this point on is to call TryInvoke instead of Invoke whenever you're publishing an event, and import the namespace of your extension method.

Posted on 8/24/2011 8:51:00 PM by Jason Nadal

Permalink | Comments |

Categories: design | codeQuality | software | refactoring | development

Tags:

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

An Exercise in Refactoring: Part 6

This post is a continuation of our previous article, a response to this smelly code.

When we left off yesterday, I mentioned that the rest of the day’s work on this refactoring (I do have a day job as well, please keep in mind!) I’d spend replacing the Grid.Columns[column][row]:Cell. Well in order to do that, I’ve introduced a new method in the IGrid interface:

public Cell GetCell(GridCoordinate coordinate)
{
    return Columns[coordinate.Column]
        [coordinate.Row];
}

This allows us to do some interesting things in the long term. But really it just makes parameter lists that are passed into methods cleaner.

Now we can get to the fun part – all those GetXKey methods… one for each direction. There’s so much duplication there! See if you can find the similarities in purpose and form between two of the four methods.

private static string GetRightKey(IGrid grid, GridCoordinate location, CellValues clickedValue)
{

    string rightKey = String.Empty;
    bool columnIsNotLastColumn = location.Column < grid.Width - 1; ;
    if (columnIsNotLastColumn
        && grid.Columns[location.Column + 1][location.Row].CellValue == clickedValue
        && IsCellInMarkableState(grid.Columns[location.Column + 1][location.Row]))
    {
        rightKey = location.MoveRight().CreateCellKey();
    }
    return rightKey;
}

private static string GetLeftKey(IGrid grid, GridCoordinate location, CellValues clickedValue)
{
    string leftKey = String.Empty;
    bool columnIsNotFirstColumn = location.Column > 0;
    if (columnIsNotFirstColumn
        && grid.Columns[location.Column - 1][location.Row].CellValue == clickedValue
        && IsCellInMarkableState(grid.Columns[location.Column - 1][location.Row]))
    {
        leftKey = location.MoveLeft().CreateCellKey();
    }
    return leftKey;
}

Any ideas? The problem is that there’s a lot of different code there. They’re looking at different criteria in that first boolean expression, but that logic is clear – is the cell (location) that’s being passed in outside the bounds of the Grid. If the cells a legitimate location, and meets the other criteria, then create the cell key.

So what we can introduce is a GridCoordinateBoundary class, representing the measurable borders (boundaries) of our Grid object.

public class GridCoordinateBoundary
{
    public int MinimumRow { get; set; }
    public int MaximumRow { get; set; }
    public int MinimumColumn { get; set; }
    public int MaximumColumn { get; set; }

    private GridCoordinateBoundary(int minimumRow, int maximumRow, int minimumColumn, int maximumColumn)
    {
        MinimumRow = minimumRow;
        MaximumRow = maximumRow;
        MinimumColumn = minimumColumn;
        MaximumColumn = maximumColumn;
    }

    public static GridCoordinateBoundary CreateFrom(int minimumRow, int maximumRow, int minimumColumn, int maximumColumn)
    {
        return new GridCoordinateBoundary(minimumRow, maximumRow, minimumColumn, maximumColumn);
    }
    
    public bool IsWithinBoundary(GridCoordinate location)
    {
        return (location.Row >= MinimumRow)
               && (location.Row < MaximumRow)
               && (location.Column >= MinimumColumn)
               && (location.Column < MaximumColumn);
    }
}

Now we can start pulling the logic together. We don’t need the 4 unique GetDownKey, GetUpKey, etc.

private static string GetKeyFor1(IGrid grid, GridCoordinate nextLocation, CellValues clickedValue)
{
    string key = String.Empty;
    if (grid.GetBoundary().
            IsWithinBoundary(nextLocation)
        && grid.GetCell(nextLocation).CellValue 
            == clickedValue
        && IsCellInMarkableState(
            grid.GetCell(nextLocation)))
    {
        key = nextLocation.CreateCellKey();
    }
    return key;
}

Note that the logic is right in the if block. Based on literature I’m currently reading (I’d highly recommend Robert C. Martin’s Clean Code so far), pulling the boolean expressions out for variable names is cleaner. This gives us the following result at the end of a short day’s work. I will spend the rest of the day getting the GetCell() method propagated to replace the Column[][] syntax that’s pervasive in the solution.

private static string GetKeyFor(IGrid grid, GridCoordinate nextLocation, CellValues clickedValue)
{
    string key = String.Empty;
    bool locationIsWithinGridBoundary = grid.GetBoundary().IsWithinBoundary(nextLocation);
    bool cellValueIsSameAsClicked = grid.GetCell(nextLocation).CellValue == clickedValue;

    if (locationIsWithinGridBoundary
        && cellValueIsSameAsClicked
        && IsCellInMarkableState(grid.GetCell(nextLocation)))
    {
        key = nextLocation.CreateCellKey();
    }
    return key;
}

Posted on 5/6/2009 7:00:35 AM by Jason Nadal

Permalink | Comments |

Categories: development | codeQuality | refactoring

Tags:

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

An Exercise in Refactoring: Part 5

This post is a continuation of our previous article, a response to this smelly code.

Remember those columns and rows everywhere, and how tough it was to swap them everywhere? Well this is a prime time to fix that. How else can we reduce parameter count, while still passing in the same information? Enter complex object.

In this case, we can gain:

  • Reduction of column / row parameters to just location
  • Give the CreateCellKey(x,y) method a home (really, it gives a key for a particular coordinate.

So our first step is to create the new object and move in the CreateCellKey method; note this is also implementing a static create method:

public class GridCoordinate
{
    public int Column { get; set;}
    public int Row { get; set; }

    public static string CreateCellKey(GridCoordinate location)
    {
        return location.Column + "," + location.Row;
    }

    public static GridCoordinate Create(int column, int row)
    {
        return new GridCoordinate {Column = column, Row = row};
    }
}

And then we have to fix the calls that just deal with columns and rows. Note the code is longer for now, but we’ll address this with reusability in the near future!

Our resulting changed code is as follows:

private static string GetRightKey(IGrid grid, GridCoordinate location, CellValues clickedValue)
{

    string rightKey = String.Empty;
    bool columnIsNotLastColumn = location.Column < grid.Width - 1; ;
    if (columnIsNotLastColumn
        && grid.Columns[location.Column + 1][location.Row].CellValue == clickedValue
        && IsCellInMarkableState(grid.Columns[location.Column + 1][location.Row]))
    {
        rightKey = GridCoordinate.CreateCellKey(GridCoordinate.Create(location.Column + 1, location.Row));
    }
    return rightKey;
}

private static string GetLeftKey(IGrid grid, GridCoordinate location, CellValues clickedValue)
{
    string leftKey = String.Empty;
    bool columnIsNotFirstColumn = location.Column > 0;
    if (columnIsNotFirstColumn
        && grid.Columns[location.Column - 1][location.Row].CellValue == clickedValue
        && IsCellInMarkableState(grid.Columns[location.Column - 1][location.Row]))
    {
        leftKey = GridCoordinate.CreateCellKey(GridCoordinate.Create(location.Column - 1, location.Row));
    }
    return leftKey;
}

private static string GetDownKey(IGrid grid, GridCoordinate location, CellValues clickedValue)
{
    string downKey = String.Empty;
    bool rowIsNotLastRow = location.Row < grid.Height - 1;
    if (rowIsNotLastRow
        && grid.Columns[location.Column][location.Row + 1].CellValue == clickedValue
        && IsCellInMarkableState(grid.Columns[location.Column][location.Row + 1]))
    {
        downKey = GridCoordinate.CreateCellKey(GridCoordinate.Create(location.Column, location.Row + 1));
    }
    return downKey;
}

private static string GetUpKey(IGrid grid, GridCoordinate location, CellValues clickedValue)
{
    string upKey = String.Empty;
    bool rowIsNotFirstRow = location.Row > 0;
    if (rowIsNotFirstRow
        && grid.Columns[location.Column][location.Row- 1].CellValue == clickedValue
        && IsCellInMarkableState(grid.Columns[location.Column][location.Row - 1]))
    {
        upKey = GridCoordinate.CreateCellKey(GridCoordinate.Create(location.Column, location.Row - 1));
    }
    return upKey;
}

private int IfContainsClickedValueThenMark(string key, IGrid grid, GridCoordinate location, CellValues clickedValue)
{
    int countOfMarkedCells = 0;
    if (key != String.Empty && !_searchedCells.Contains(key))
    {
        _searchedCells.Add(key, String.Empty);
        countOfMarkedCells += MarkConnectingCellsRecursive(grid, location, clickedValue);
    }
    return countOfMarkedCells;
}

private int MarkConnectingCellsRecursive(IGrid grid, GridCoordinate location, CellValues clickedValue)
{
    //set the current location to be clicked.
    grid.Columns[location.Column][location.Row].CellState = CellStates.Down;

    //we seed the count at one here, because at least the current cell is to be
    //marked.
    int countOfMarkedCells = 1; 

    //look all around our current cell for other cells of the same value.
    string rightKey = GetRightKey(grid, location, clickedValue);
    string leftKey = GetLeftKey(grid, location, clickedValue);
    string downKey = GetDownKey(grid, location, clickedValue);
    string upKey = GetUpKey(grid, location, clickedValue);

    //if the keys ARE the same value, look around them for the same value, mark them, and add them
    //to the count
    countOfMarkedCells += IfContainsClickedValueThenMark(rightKey, grid, GridCoordinate.Create(location.Column + 1, location.Row), clickedValue);
    countOfMarkedCells += IfContainsClickedValueThenMark(leftKey, grid, GridCoordinate.Create(location.Column - 1, location.Row), clickedValue);
    countOfMarkedCells += IfContainsClickedValueThenMark(downKey, grid, GridCoordinate.Create(location.Column, location.Row + 1), clickedValue);
    countOfMarkedCells += IfContainsClickedValueThenMark(upKey, grid, GridCoordinate.Create(location.Column, location.Row- 1), clickedValue);

    return countOfMarkedCells;
}

Now that we’ve done that, we can really do some cool things. Since we have a reference to a distinct location as an object, that object knows where it is. You can treat it like a plotter. Given a location, move up, down, left, right.(Note, we’ll probably want to rename this object to something like GridPlotter, so it makes more sense… this would have a GridCoordinate object, but for now, we’ll place the methods right in the GridCoordinate object:

public GridCoordinate MoveUp()
{
    return Create(Column, Row - 1);
}

public GridCoordinate MoveDown()
{
    return Create(Column, Row + 1);
}

public GridCoordinate MoveLeft()
{
    return Create(Column - 1, Row);
}

public GridCoordinate MoveRight()
{
    return Create(Column + 1, Row);
}

public string CreateCellKey()
{
    return CreateCellKey(this);
}

This lets you refactor code like follows. Instead of the +1 and –1 everywhere:

countOfMarkedCells += 
    IfContainsClickedValueThenMark(
    rightKey, grid, 
    GridCoordinate.Create(location.Column + 1, 
        location.Row), 
    clickedValue);

You can just put:

countOfMarkedCells += IfContainsClickedValueThenMark(
    rightKey, grid, 
    location.MoveRight(), 
    clickedValue);

At this point, unit tests all still pass, and app feels pretty good still, so this is a pretty successful refactoring process thus far. For the time until the next post, I’ll be making sure all areas of code referencing this class (the GridController class) no longer use pure “int column, int row” values, but rather use the more robust GridCoordinate class.

After this is complete, the GridCoordinate class will be renamed, and co-located near the Grid itself. Right now, this is only referenced by the GridController, but in reality, the GridCoordinate should serve as the base of all Grid cell lookups. (Rather than always using the Grid.Columns[column][row]:Cell).

Posted on 5/5/2009 6:42:40 AM by Jason Nadal

Permalink | Comments |

Categories: development | patterns | refactoring | codeQuality

Tags:

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5