Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove superfluous generic arguments from a method

Tags:

c#

generics

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.

like image 430
Nick Avatar asked Apr 28 '26 13:04

Nick


2 Answers

Generics are meant for compile time, not runtime. If you need it at runtime you'd have to use reflection and do something like this:

public TOut CreateRequest3<TOut, TModelIn>(TModelIn data) {
    var type = typeof(TOut).GetProperty("Data").PropertyType;
    return typeof(Mapper).GetMethod("CreateRequest2").MakeGenericMethod(new Type[] {TOut, type, TModelIn} ).Invoke(this, new Object[] {data});
}

This is quite ugly, perhaps there is a different approach to what you wish to achieve?

like image 191
EpicSam Avatar answered May 01 '26 02:05

EpicSam


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

like image 33
CSharpie Avatar answered May 01 '26 03:05

CSharpie



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!