Skip to main content
added 826 characters in body
Source Link
Johnbot
  • 3.1k
  • 16
  • 19

Instead of using a ConcurrentDictionary as a cache, and having to cast, you can use an embedded generic static class. The static construction is lazy and threadsafe by design. To make it work you have to create and open delegate (an instance method with no "this") for the mapper function:

public class Mapper
{
    public Mapper()
    {
        // dependencies are injected in the constructor
        // not shown here for simplicity
    }

    public object Map<T>(T item)
    {
        var mapper = MapperCache<T>.Map;
        return mapper(this, item);
    }

    // example methods
    public object Map(SomeObject item) { return new object(); }

    public object Map(SomeTotallyDifferentObject item) { return new object(); }

    public object Map(Guid item) { return new object(); }
    
    private static class MapperCache<T>
    {
        public static readonly Func<Mapper, T, object> Map { get; }Map;
        
        static MapperCache()
        {
            var methodmapMethod = typeof(Mapper).GetMethod("Map", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(T) }, null);
            var 
 @delegate               Map = mapMethod != null ? (Func<Mapper, T, object>)Delegate.CreateDelegate(typeof(Func<Mapper, T, object>), methodmapMethod);
            Map = @delegate;                          : (mapper, item) => { throw new InvalidOperationException($"{nameof(Mapper)} cannot map from {typeof(T).Name} to object"); };
        }
    }
}

The first time you reference the static property, Map, the MapperCache<T> for the given type T will be initialized. By setting up the delegate creation in the static constructor you guarantee that it will only run once for each T. If any exceptions are thrown in the static constructor the type will fail to load and cannot be used at runtime.

The same trick is used in System.Data.DataRowExtensions (source) to cache converters.

Instead of using a ConcurrentDictionary as a cache, and having to cast, you can use an embedded generic static class. The static construction is lazy and threadsafe by design. To make it work you have to create and open delegate (an instance method with no "this") for the mapper function:

public class Mapper
{
    public Mapper()
    {
        // dependencies are injected in the constructor
        // not shown here for simplicity
    }

    public object Map<T>(T item)
    {
        var mapper = MapperCache<T>.Map;
        return mapper(this, item);
    }

    // example methods
    public object Map(SomeObject item) { return new object(); }

    public object Map(SomeTotallyDifferentObject item) { return new object(); }

    public object Map(Guid item) { return new object(); }
    
    private static class MapperCache<T>
    {
        public static Func<Mapper, T, object> Map { get; }
        
        static MapperCache()
        {
            var method = typeof(Mapper).GetMethod("Map", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(T) }, null);
            var @delegate = (Func<Mapper, T, object>)Delegate.CreateDelegate(typeof(Func<Mapper, T, object>), method);
            Map = @delegate;
        }
    }
}

Instead of using a ConcurrentDictionary as a cache, and having to cast, you can use an embedded generic static class. The static construction is lazy and threadsafe by design. To make it work you have to create and open delegate (an instance method with no "this") for the mapper function:

public class Mapper
{
    public Mapper()
    {
        // dependencies are injected in the constructor
        // not shown here for simplicity
    }

    public object Map<T>(T item)
    {
        var mapper = MapperCache<T>.Map;
        return mapper(this, item);
    }

    // example methods
    public object Map(SomeObject item) { return new object(); }

    public object Map(SomeTotallyDifferentObject item) { return new object(); }

    public object Map(Guid item) { return new object(); }
    
    private static class MapperCache<T>
    {
        public static readonly Func<Mapper, T, object> Map;
        
        static MapperCache()
        {
            var mapMethod = typeof(Mapper).GetMethod("Map", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(T) }, null);
             
                Map = mapMethod != null ? (Func<Mapper, T, object>)Delegate.CreateDelegate(typeof(Func<Mapper, T, object>), mapMethod)
                                        : (mapper, item) => { throw new InvalidOperationException($"{nameof(Mapper)} cannot map from {typeof(T).Name} to object"); };
        }
    }
}

The first time you reference the static property, Map, the MapperCache<T> for the given type T will be initialized. By setting up the delegate creation in the static constructor you guarantee that it will only run once for each T. If any exceptions are thrown in the static constructor the type will fail to load and cannot be used at runtime.

The same trick is used in System.Data.DataRowExtensions (source) to cache converters.

Source Link
Johnbot
  • 3.1k
  • 16
  • 19

Instead of using a ConcurrentDictionary as a cache, and having to cast, you can use an embedded generic static class. The static construction is lazy and threadsafe by design. To make it work you have to create and open delegate (an instance method with no "this") for the mapper function:

public class Mapper
{
    public Mapper()
    {
        // dependencies are injected in the constructor
        // not shown here for simplicity
    }

    public object Map<T>(T item)
    {
        var mapper = MapperCache<T>.Map;
        return mapper(this, item);
    }

    // example methods
    public object Map(SomeObject item) { return new object(); }

    public object Map(SomeTotallyDifferentObject item) { return new object(); }

    public object Map(Guid item) { return new object(); }
    
    private static class MapperCache<T>
    {
        public static Func<Mapper, T, object> Map { get; }
        
        static MapperCache()
        {
            var method = typeof(Mapper).GetMethod("Map", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(T) }, null);
            var @delegate = (Func<Mapper, T, object>)Delegate.CreateDelegate(typeof(Func<Mapper, T, object>), method);
            Map = @delegate;
        }
    }
}