简体   繁体   中英

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?

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> .

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);
}

A better way to solve this might be to change signature like this:

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

You don't really need to accept a List , because you are not doing anything "list-specific" with the value, and AutoMapper understands any "collection" type you pass to it. IEnumerable<T> is enough, and because it's covariant with respect to T , it now works fine in your case:

// works
List<FlightRouteDTO> bar = new List<FlightRoute>().ToDTO<FlightRouteDTO>();

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