简体   繁体   中英

Generic type constraint and variance

I am building a simple type mapper similar to AutoMapper but with a more dynamic behaviour. The caller can decide to filter RecordStatus == RecordStatus.Deleted records when mapping from entity framework models.

Abstract mappers:

public interface IMapper<in TIn, out TOut>
{
    TOut Map(TIn input);
}

public interface IRecordStatusFilterable
{
    string RecordStatus { get; }
}

public abstract class RecordStatusFilterableMapperBase<TIn, TOut> : IMapper<TIn, TOut>
{
    private readonly bool _filterDeletedRecords;

    protected RecordStatusFilterableMapperBase(bool filterDeletedRecords)
    {
        _filterDeletedRecords = filterDeletedRecords;
    }

    protected bool FilterDeletedRecords
    {
         get { return _filterDeletedRecords; }
    }

    public abstract TOut Map(TIn input);
}

public class MultiLookupValuesMapper : RecordStatusFilterableMapperBase<IEnumerable<Lookup>, string>
{
    private static readonly Func<Lookup, bool> _predicate = 
        filterable => filterable.RecordStatus == RecordStatus.Active;

    protected MultiLookupValuesMapper(bool filterDeletedRecords) : base(filterDeletedRecords)
    {
    }

    public override string Map(IEnumerable<Lookup> input)
    {
        var inputList = input as IList<Lookup> ?? input.ToList();
        if (!inputList.Any())
        {
            return string.Empty;
        }

        if (FilterDeletedRecords)
        {
            inputList = (IList<Lookup>)inputList.Where(_predicate);
        }

        return string.Join(", ", inputList.Select(l => l.Value));
    }
} 

Concrete Mappers:

public class FooMapper<TRecordStatusFilterable> : RecordStatusFilterableMapperBase<Foo, FooViewModel> 
    where TRecordStatusFilterable : class, IRecordStatusFilterable
{
    private readonly IMapper<IEnumerable<TRecordStatusFilterable>, string> _multiLookupValueMapper;

    public FooMapper(IMapper<IEnumerable<TRecordStatusFilterable>, string> multiLookupValueMapper,
                     bool filterDeletedRecords) : base(filterDeletedRecords)
    {
        _multiLookupValueMapper = multiLookupValueMapper;
    }

    public override FooViewModel Map(Foo input)
    {
        return new FooViewModel
        {
            // Error here
            BarLookupValues = _multiLookupValueMapper.Map(input.Lookups)
        };
    }
}

Entity Framework model:

public class Foo
{
    public ICollection<Lookup> Lookups { get; set; }
}

public class Lookup : IRecordStatusFilterable
{
    public string Value { get; set; }

    public string RecordStatus { get; set; }
}

ViewModels:

public class FooViewModel
{
    // ICollection<Lookup> => string
    public string BarLookupValues { get; set; }
}

I got a compile error:

Argument 1: cannot convert from 'System.Collections.Generic.IEnumerable<Lookup>' to 'System.Collections.Generic.IEnumerable<TRecordStatusFilterable>'

But my Lookup class does fulfill the generic type parameter constraint as it implements IRecordStatusFilterable . Can anyone shed some light on this?

Actually a lot of the code is irrelevant to the actual problem. Here is a simpler version that, hopefully, illustrates it better:

class MyList<T>
    where T : class, IConvertible
{
    private List<T> list = new List<T>();

    public void Add(string s)
    {
        list.Add(s); // error
    }        
}

Yes T is constrained, and string fits the constraints, but that doesn't mean you can go and add string to a List of arbitrary T's. That wouldn't type safe.

If I defined

class Bar : IConvertible { /* left out IConvertible impl */ }

and made a var bars = new MyList<Bar>() it is obvious that adding a string to bars is a problem for that code in the generic class.

You've just got a more complex version of this and I'm not 100% sure what exactly you are trying to express. Perhaps the class FooMapper shouldn't be generic at all and should just take an instance of IMapper<IEnumerable<Lookup>, string> .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM