Show / Hide Table of Contents

The handler pattern

Often the extensions you are registering are handlers designed to act upon other objects.

There are 2 interfaces provided to help with identifying the handlers appropriate for the target objects.

  • IHandlesType: targets all objects of a certain type
  • IHandlesObject: targets objects based on the object instance (state)

IHandlesType Example

Extend IHandlesType and implement the HandlesType property. In this case the decision about whether an item will be handled is based on its type matching or deriving from the HandlesType property.

public interface IKeyGenerator : IHandlesType { }

public class OrderKeyGenerator : IKeyGenerator
{
    public Type HandlesType => typeof(Order);
}

IHandlesObject Example

Extend IHandlesObject and implement the CanHandle method. In this case you need to provide the logic to determine whether or not to handle the object.

public interface IKeyGenerator : IHandlesObject { }

public class OrderKeyGenerator : IKeyGenerator
{
    public bool CanHandle(object item)
    {
        // ...
    }
}

A base class is a great idea

Not always required, but sometimes it helps to just simplify the implementation of your handlers and allow your extensions to leverage generic types.

public interface IKeyGenerator : IHandlesType {
    string GenerateKey(object item);
}

public abstract class KeyGeneratorBase<T> : IKeyGenerator
{
    public Type HandlesType => typeof(T);

    public string GenerateKey(object item)
    {
        if (item is T t)
            return GenerateKey(t);
        else
            throw new Exception("Incorrect type for item.");
    }

    public abstract string GenerateKey(T item);
}

public class OrderKeyGenerator : KeyGeneratorBase<Order>
{
    public override string GenerateKey(Order item)
    {
        /// ...
    }
}

Use the Handles() extension method in your service or provider

The Handles extension methods handle both IHandlesType and IHandlesObject as a transparent way to identify handlers for your objects.

public class KeyProvider : IKeyProvider
{
    private readonly IEnumerable<IKeyGenerator> _keyGenerators;

    public KeyProvider(IEnumerable<IKeyGenerator> keyGenerators)
    {
        _keyGenerators = keyGenerators;
    }

    public string GenerateKey(object item)
    {
        return _keyGenerators.FirstOrDefault(p => p.Handles(item))?.GenerateKey(item);
    }
}

Improve performance by caching the handler lookup

This is not always possible, but when your handler is based on type, it's an essential performance improvement.

public class KeyProvider : IKeyProvider
{
    private readonly IEnumerable<IKeyGenerator> _keyGenerators;
    private readonly ConcurrentDictionary<Type, IKeyGenerator> _keyGeneratorLookupCache = new ConcurrentDictionary<Type, IKeyGenerator>();

    public KeyProvider(IEnumerable<IKeyGenerator> keyGenerators)
    {
        _keyGenerators = keyGenerators;
    }

    public string GenerateKey(object item)
    {
        if (item == null)
            return null;

        return _keyGeneratorLookupCache.GetOrAdd(item.GetType(),
                t => _keyGenerators.FirstOrDefault(p => p.Handles(t))
            )?.GenerateKey(item);
    }
}
  • Improve this Doc
In This Article
Back to top Generated by DocFX