Search code examples
c#.netgenericscovariance

C# covariant/contravariant types utilizing generics with Dictionary


i need to help with solving Covariant situation in my Code.

Let me show the situation on example:

abstract class BaseSource { }
abstract class BaseTarget { }

class SourceEntityA : BaseSource { }
class TargetEntityB : BaseTarget { }

interface IEntityMapper { }
interface IEntityMapper<in TSource, out TTarget>: IEntityMapper
{
    TTarget Map(TSource entity);
}


abstract class BaseEntityMap<TSource, TTarget> : IEntityMapper<TSource, TTarget>, IEntityMapper
{
    public abstract TTarget Map(TSource entity);
}

class OrderEntityMapper : BaseEntityMap<SourceEntityA, TargetEntityB>
{
    public override TargetEntityB Map(SourceEntityA entity)
    {
        //do some map stuff
        return null;
    }
}

class GoodsEntityMapper : BaseEntityMap<SourceEntityC, TargetEntityD>
{
    public override TargetEntityD Map(SourceEntityC entity)
    {
        //do some map stuff
        return null;
    }
}

class Workflow
{
    IDictionary<Type, IEntityMapper> MappersMap = new Dictionary<Type, IEntityMapper>();
    public MappingArchestrator()
    {
        MappersMap.Add(typeof(SourceEntityA), new OrderEntityMapper());
        MappersMap.Add(typeof(SourceEntityC), new GoodsEntityMapper());
        //....more mappers
    }
    private IEnumerable<BaseSource> GetSourceEntities()
    {

        //return source data
        return null;
    }
    public void Execute()
    {
        foreach (var m in MappersMap.Keys)
        {
            var mapper = MappersMap[m];

            var entities = GetSourceEntities();

            foreach (var entity in entities)
            {
                //Need to handle this situations with correct covariations.
                var target = (IEntityMapper<BaseSource, BaseTarget>mapper).Map(entity);
                //.... code continues
            }
        }
    }
}

Shortly i need Dictionary of different types of mappers and use them Execute method in common code to handle workflow.

Could you guide me to a better code or fix an existing solution? Is it even possible?

Or i have to forget generics here and use BaseClasses in Map method and cast them to type what i need.

Something like this

abstract class BaseEntityMap : IEntityMapper
{
    public abstract BaseTarget Map(BaseSource entity);
}

Thank you


Solution

  • You can apply generic constraints in BaseEntityMap<TSource, TTarget> class to your BaseSource and BaseTarget classes (or apply constraints in IEntityMapper<in TSource, out TTarget> interface and get rid of base abstract class)

    abstract class BaseEntityMap<TSource, TTarget> : IEntityMapper<TSource, TTarget>
        where TSource : BaseSource
        where TTarget : BaseTarget
    {
        public abstract TTarget Map(TSource entity);
    }
    

    Then declare mappers dictionary like below, using Func<BaseSource, BaseTarget> delegate. It should be fine, since you've constrained your BaseEntityMap<TSource, TTarget> and Func<in T, out TResult> declaration supports contravariance for the input parameter and covariance for the result

    IDictionary<Type, Func<BaseSource, BaseTarget>> MappersMap = new Dictionary<Type, Func<BaseSource, BaseTarget>>();
    
    public Workflow()
    {
        var orderMapper = new OrderEntityMapper();
        MappersMap.Add(typeof(SourceEntityA), source => orderMapper.Map((SourceEntityA)source));
        var goodsMapper = new GoodsEntityMapper();
        MappersMap.Add(typeof(SourceEntityC), source => goodsMapper.Map((SourceEntityC)source));
        //....more mappers
    }
    

    The usage example

    foreach (var entity in entities)
    {
        var target = mapper(entity);
        //.... code continues
    }