Nick Nick - 22 days ago 8
C# Question

Remove superfluous generic arguments from a method

I am attempting to clean up a lot of my code which relates to creating web service requests, and mapping back and forth between view models and data transfer objects.
I currently have the following setup in my code:

public class Request1 : IRequest<Type1>
{
public Type1 Data { get; set; }
}

public interface IRequest<T>
{
T Data { get; set; }
}

public class Type1
{
string Account {get; set; }
...
}

public static class Mapper
{
public static TOut CreateRequest<TOut, TIn>(TIn data) where TIn : IRequest<TIn>, new()
{
var request = new TOut();
request.Data = data;
return request;
}
}


The above enables me to use generics to create the
Request1
object by passing in the data really simply.

var model = new ViewModel();
var data = Mapper.mapFromViewModel(model);
var request = Mapper.CreateRequest<Request1,Type1>(data);


This is GREAT in my opinion, however it has one flaw.

The data I pass in has to be of the dto type (
Type1
) in the request. What I would like to do is pass in the data in its raw form, so in the UI I would be passing in the view model data, and the type of Request I want. The method would work out the mapping between the view model and the dto type, and then create the request for me.

So if I use a bit of
AutoMapper
to handle the mapping between the view model and the dto type I can get to this:

public TOut CreateRequest2<TOut, TDto, TModelIn>(TModelIn data)
where TOut : IRequest<TDto>, new()
{
var request = new TOut();
request.Data = Map<TModelIn, TDto>(data);
return request;
}


Which I can use like this:

var model = new ViewModel();
var request = Mapper.CreateRequest2<Request1,Type1,ViewModel>(model);


This is almost there... but this is where I hit the wall of my knowledge.

I would like to be able to cut the
TDto
requirement from the call, so the caller only has to know about the
Request
, and the
ViewModel
types.

Something like:

public TOut CreateRequest3<TOut, TModelIn>(TModelIn data)
{
// Cast? the TOut to IRequest<>
// Extract the TDto type from the IRequest<> object and
// pass the TDto type into the method I created earlier.
return CreateRequest2<TOut,TDto,TModelIn>(data);
}


Does anyone know if this can be achieved?

The upside of this is to remove the need for my UI code to know what DTO is required on a message, and let that be handled by automapper based on the view model it is given.

UPDATE:
So following suggestions by @EpicSam I now have the following that works:

public TOut CreateRequest3<TOut, TModelIn>(TModelIn data) where TOut : class, new()
{
var interfaces = typeof(TOut).GetInterfaces().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IRequest<>));
var dtoType = interfaces?.GenericTypeArguments.FirstOrDefault();

var result = typeof(Mapper)
.GetMethod(nameof(Mapper.CreateRequest))
.MakeGenericMethod(typeof(TOut), dtoType, typeof(TModelIn))
.Invoke(this, new object[ ] {data});

return result as TOut;
}


This is ugly, but is ok, as its only ever written once. But.. what other things do I need to know about this code. As it uses reflection, is it going to hammer my performance, or should I not even worry about it....

UPDATE 2:
The reflection code is about 3x slower than the original code I posted. So even though I could choose to make my code nicer and cleaner, I'll opt to keep it as it is, and work more efficiently.

Answer

You can do it like this, abusing extension methods:

public interface IRequest
{
}
public interface IRequest<T> : IRequest
{
    T Data { get; set;}
}

public static class Mapper 
{ 
    public static TOut CreateRequest<TOut>() where TOut : IRequest, new() 
    {
        return new TOut();
    }

    public static IRequest<TDto> From<TDto,TModel>(this IRequest<TDto> request, TModel data)
    {
        request.Data=Map<TModel,TDto>(data);
        return request;
    }

    public static TOut Map<TIn,TOut>(TIn input)
    {
        // Only for this example, you need to provide your own implemenation.
        return (TOut)(object)((Model)(object)input).Value;
    }

}

Then you can invoke it like this:

Mapper.CreateRequest<SomeRequestType>().From(myModel);

See here