Providing extensible classes: An alternative to inheritance and events

I have a list that is cached. When I come to refresh the list I don’t want to just throw it away and start again, so I use the following.
private void UpdateChildren(IEnumerable<Guid> fromDb, IEnumerable<Model> cached )
{
    List<Model> unseen = new List<Model>(cached); 
 
    foreach (Model child in cached)
    {
        Guid childID = child.Id;
        if (fromDb.Contains(childID) == false)
            cached.Add(CreateModel(childID));
        //seen it now
        unseen.Remove(child);  
    }
    //Delete missing items
    foreach (Model child in unseen)
    {
        DisposeModel(child);
        cached.Remove(child);
    }
}
 
private void DisposeModel(Model child)
{
    // Maybe do nothing
} 
 
private Model CreateModel(Guid child)
{
    return new Model(child);
} 

This works fine but it isn’t very generic. Let’s make some changes

Creating a class

Currently this isn’t really a class, it could be expressed as three static methods. By including the cached list we can start to provide a reusable class with state.

class CachedCollection
{
    private List<Model> _cached = new List<Model>();
 
    private void UpdateChildren(IEnumerable<Guid> fromDb)
    {
        List<Model> unseen = new List<Model>(_cached); 
 
        foreach (Model child in _cached)

Mapping from Guid to Model

Currently we know how to compare a Guid and a Model, since the model has a property that exposes that Guid again. We cannot be certain that this so the case every time so now we need to go for a more generic pairing. In this case I suggest moving from a List<Model> to a Dictionary<Guid, Model>.

 

private Dictionary<Guid, Model> _known = new Dictionary<Guid, Model>();
public void UpdateItems(IEnumerable<Guid> iEnumerable)
{
    ...
    foreach (Model item in iEnumerable)
    {
        if (_known.ContainsKey(item))

Now our paired relationship is completely external to the classes themselves. This is a common feature of code that has been made reusable, in that the method of relating things is sub optimal, in this case with a memory overhead. The dictionaries key hashing lookup will provide a performance boost whether that is required for this application or not.

Applying Generic types

Not much makes a code more generic than applying Generic types, in this case we can refactor the fixed types, but this is full of issues. In this case lets consider converting the Guid to TActual and the Model to TDesired

private Model CreateModel(Guid child)
{
    return new Model(child);
}

If we look at the CreateModel method we note that this is currently creating a new TDesired from a TActual. We can add a new constraint to the the class defintion, but I don’t know of a way to specify that a Type has a particular constructor that takes a single argument.

//Just gets me a ') expected' error.
class CachedCollection<TActual, TDesired> where TDesired:new(TActual)

I can of course make my class abstract and leave it to the derivation to handle, but that means I need a derived class.

abstract class CachedCollection<TActual, TDesired> 
{
    private List<Model> _cached = new List<Model>();
 
    private void UpdateChildren(IEnumerable<TActual> fromDb)
    {
        List<TDesired> unseen = new List<TDesired>(_cached); 
 
        foreach (TDesired child in _cached)
        {
            TActual childID = child.Id;
            if (fromDb.Contains(childID) == false)
                _cached.Add(CreateModel(childID));
            //seen it now
            unseen.Remove(child);  
        }
        //Delete missing items
        foreach (TDesired child in unseen)
        {
            DisposeModel(child);
            _cached.Remove(child);
        }
    }
 
    private abstract void DisposeModel(TDesired child);
    private abstract TDesired CreateModel(TActual child);
}

Of course when we consider using abstract and virtual methods, we start to consider functions as pieces of code that can be referred to. Before DotNet 2.0 there the only way to do this was to use delegates. Now where these functions are optional you may see them exposed as events. These days we have some alternatives.

Generic delegates

There are three great Generic delegate types available since DotNet 2.0

//http://msdn.microsoft.com/en-us/library/kt456a2y.aspx
public delegate TOutput Converter<TInput, TOutput>(TInput input)
 
//http://msdn.microsoft.com/en-us/library/system.action.aspx
public delegate void Action<T>(T input)
 
//http://msdn.microsoft.com/en-us/library/bfcke1bz.aspx
public delegate bool Predicate<T>(T input)
 
These are great building blocks and along with 3.5’s lambda syntax let us provide functions almost as simply as by deriving a class. For example we can convert
private abstract TDesired CreateModel(TActual child);
public void Update()
{
    ...
    x = CreateModel(...);
    ...
}
Into
private Converter<TActualItem, TAlternateItem> _createAlternateItem 
    = new Converter<TActualItem, TAlternateItem>(
        (TActualItem actual) => {throw new NotImplementedException();}
    );
 
public void Update()
{
    ...
    x = _createAlternateItem (...);
    ...
}

Conclusion

For me, this is very powerful. It means we can take our original example and produce the following non-abstract class.

class CollectionSync<TActualItem, TAlternateItem>
{
    public CollectionSync(Converter<TActualItem, TAlternateItem> createAlternateItem)
    {
        _createAlternateItem = createAlternateItem;
    }
 
    public CollectionSync(Converter<TActualItem, TAlternateItem> createAlternateItem, 
        IEnumerable<TActualItem> list) 
        : this(createAlternateItem)
    {
        foreach (TActualItem actual in list)
        {
            _known.Add(actual, _createAlternateItem(actual));
        }
    }
 
    private Dictionary<TActualItem, TAlternateItem> _known 
        = new Dictionary<TActualItem, TAlternateItem>();
    private Converter<TActualItem, TAlternateItem> _createAlternateItem = null;
    private Action<TAlternateItem> _disposeAlternate 
        = new Action<TAlternateItem>((TAlternateItem alternate) => { });
    private Action<KeyValuePair<TActualItem, TAlternateItem>> _udateSingleAlternate 
        = ((KeyValuePair<TActualItem, TAlternateItem> kvp) => { });
 
    public void UpdateItems(IEnumerable<TActualItem> iEnumerable)
    {
        List<TActualItem> unseen = new List<TActualItem>(_known.Keys);
 
        foreach (TActualItem item in iEnumerable)
        {
            if (_known.ContainsKey(item))
            {
                _udateSingleAlternate(
                    new KeyValuePair<TActualItem, TAlternateItem>
                        (item, _known[item]));
                unseen.Remove(item);
            }
            else
            {
                TAlternateItem newLVI = _createAlternateItem(item);
                _known[item] = newLVI;
            }
        }
        //Delete missing items
        foreach (TActualItem item in unseen)
        {
            _disposeAlternate(_known[item]);
            _known.Remove(item);
        }
    }
}
Advertisement