简体   繁体   中英

How to simulate method overloading based on generic return type in c#

I have a read model as IQueryable<CustomType> , I use this inside my Web application. A lot of time I need to extract from this read model different View Model.

I use to write extension method like:

public static ViewModelA AsViewModelA(this IQueryable<CustomType> query) 
{ 
    var vm = view
              .Select(x => new ViewModelA
              {
                  Something = x.Something
              }).FirstOrDefault();

    return vm;
}

public static ViewModelB AsViewModelB(this IQueryable<CustomType> query) 
{
    var vm = view
              .Select(x => new ViewModelB
              {
                  SomethingElse = x.SomethingElse
              }).FirstOrDefault();

    return vm;
}

This do the job but I don't like the mess generated with method names; a more generic way, something like this would be preferable:

query.AsViewModel<ViewModelA>()

I know that return type is not intended as method signature (so no overload applies) and I know that generic type is not sufficient to make an overload. What I would is a mechanism to just simulate overloading based on generic type. This mechanism should avoid a main method with cascading if/then/else. There is a way? Maybe with dynamics?

One option is to have a map from the type to a conversion of CustomType to that type. So it would look something like:

private static readonly Dictionary<Type, Expression> Mappings = 
    new Dictionary<Type, Expression>
    {
        { typeof(ViewModelA),
          Helper<ViewModelA>(x => new ViewModelA { Something = x.Something }) },
        { typeof(ViewModelB),
          Helper<ViewModelB>(x => new ViewModelB { SomethingElse = x.SomethingElse }) },
        ...
    }

// This method just helps avoid casting all over the place.
// In C# 6 you could use an expression-bodied member - or add a 
private static Expression<Func<CustomType, T>> Helper<T>
    (Expression<Func<CustomType, T>> expression)
{
    return expression;
}

public static T AsViewModel<T>(this IQueryable<CustomType> query) 
{ 
    Expression rawMapping;
    if (!Mappings.TryGetValue(typeof(T), out rawMapping))
    {
        throw new InvalidOperationException("Or another exception...");
    }
    // This will always be valid if we've set up the dictionary properly
    var mapping = (Expression<Func<CustomType, T>>) rawMapping;
    return view.Select(mapping).FirstOrDefault();
}

You can make the dictionary construction a bit cleaner with a bit more up-front code.

Well, yes, you can use dynamic :

private static ViewModelA AsViewModelInternal(this IQueryable<CustomType> query, 
                                              ViewModelA dummy) { ... }

private static ViewModelB AsViewModelInternal(this IQueryable<CustomType> query, 
                                              ViewModelB dummy) { ... }

public static T AsViewModel<T>(this IQueryable<CustomType> query)
{
  return (T)query.AsViewModelInternal(default(T));
}

Make sure to handle a non-existing overload, of course :) The easiest way is to add an overload that takes object as the last argument, so that you basically have a "fallback overload".

However, I wouldn't recommend that. One of the great benefits of generics is you get great compile-time checks. This generic method pretends to accept all possible T's, but it actually doesn't. It's the equivalent of taking object instead of ViewModelA / ViewModelB .

It's not like there's a world's difference between

query.AsViewModelB()

and

query.AsViewModel<ViewModelB>()

I'd only use the alternative if you often find yourself having to use a generic type argument when calling AsViewModel , ie when you don¨t know the specific 'type in advance.

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