Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Extension method on List with specific type

I have the following 2 extension methods

namespace Services.Resources.Extensions
{
    public static class DataMapExtensions
    {
        public static T ToDTO<T>(this BaseModel model)
        {
            return Mapper.Map<T>(model);
        }

        public static List<T> ToDTO<T>(this List<BaseModel> models)
        {
            return Mapper.Map<List<T>>(models);
        }

    }
}

The first method works perfectly fine.

//Note: FlightRoute inherits BaseModel
FlightRouteDTO foo = new FlightRoute().ToDTO<FlightRouteDTO>(); //This works!

However, the second method does not seem to work.

List<FlightRouteDTO> bar = new List<FlightRoute>().ToDTO<FlightRouteDTO>(); //This doesn't work!

The compiler is saying

Error CS1929 'List< FlightRoute>' does not contain a definition for 'ToDTO' and the best extension method overload 'DataMapExtensions.ToDTO< FlightRouteDTO>(List< BaseModel>)' requires a receiver of type 'List< BaseModel>'

But FlightRoute is of type BaseModel. If I change the type of bar to explicitly be List<BaseModel> ... then the problem goes away.

List<FlightRouteDTO> bar = new List<BaseModel>().ToDTO<FlightRouteDTO>(); //Why does it only work this way?

Am I missing something obvious?

like image 918
Dillon Drobena Avatar asked Dec 14 '22 17:12

Dillon Drobena


2 Answers

That's just the expected behavior: you are trying to use a List<FlightRoute> as a List<BaseModel>, but just because FlitghtRoute inherits from BaseModel doesn't make List<FlitghtRoute> inherit from List<BaseModel>: they are completely different types.

What you could do, instead, is to leverage the use of Covariance, using interfaces instead of concrete types.

By changing your method signature to the following, you will notice that no compiler error will be generated:

public static List<T> ToDTO<T>(this IEnumerable<BaseModel> models)
{
    return Mapper.Map<List<T>>(models);
}

That's because IEnumerable<T> is an interface with a covariant type parameter. By looking at the reference source, you will notice that this interface is declared with out T as generic type parameter, which indicates that T is covariant, and may be replaced by any inherited type when we use IEnumerable<T>.

like image 135
Federico Dipuma Avatar answered Dec 28 '22 15:12

Federico Dipuma


You could introduce a second type parameter with a constraint:

public static List<T> ToDTO<T, K>(this List<K> models) where K : BaseModel
{
    return Mapper.Map<List<T>>(models);
}
like image 26
adjan Avatar answered Dec 28 '22 14:12

adjan