2016-11-14 4 views
3

Ich versuche, eine Menge meines Codes zu bereinigen, der sich auf das Erstellen von Web-Service-Anforderungen bezieht und zwischen Ansichtsmodellen und Datenübertragungsobjekten hin und her mappt. Im Moment habe ich die folgende Einstellung in meinem Code:Entfernen überflüssige generische Argumente von einer Methode

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

Die oben ermöglicht es mir, Generika zu verwenden, um die Request1-Objekt zu erstellen, indem man wirklich einfach in den Daten.

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

Dies ist GREAT meiner Meinung nach, aber es hat einen Schönheitsfehler.
Die Daten, die ich übergebe, müssen vom Typ dto sein (Type1) in der Anfrage. Was ich tun möchte ist, die Daten in ihrer rohen Form zu übergeben, also würde ich in der Benutzeroberfläche die Daten des Ansichtsmodells und die Art der gewünschten Anfrage übergeben. Die Methode würde die Zuordnung zwischen dem Ansichtsmodell und dem Dto-Typ ausarbeiten und dann die Anfrage für mich erstellen.

Also, wenn ich ein bisschen AutoMapper verwenden, um die Zuordnung zwischen der View-Modell und dem dto Typ verarbeiten kann ich diese:

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

Was ich wie diese verwenden:

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

Das ist fast da ... aber hier bin ich an der Wand meines Wissens.
Ich möchte in der Lage sein, die TDto Anforderung von dem Anruf zu schneiden, so muss der Anrufer nur über die Request und die ViewModel Typen kennen.

Etwas wie:

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

Wer weiß, ob dies erreicht werden kann?

Die Oberseite von diesem ist, um die Notwendigkeit für meinen UI-Code zu entfernen, um zu wissen, welche DTO für eine Nachricht erforderlich ist, und lassen Sie das von Autoadapter basierend auf dem Ansichtsmodell behandelt werden, das es angegeben wird.

UPDATE: So folgende Vorschläge von @EpicSam Ich habe jetzt die folgende, die funktioniert:

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

Das ist hässlich, aber es ist in Ordnung, wie der immer nur einmal geschrieben. Aber was muss ich sonst über diesen Code wissen? Wie es Reflexion verwendet, ist es dabei, meine Leistung hämmern, oder sollte ich auch nur darüber keine Sorgen ....

UPDATE 2: Der Reflexions Code ist etwa 3x langsamer als die Original-Code habe ich geschrieben. Obwohl ich mich entscheiden könnte, meinen Code schöner und sauberer zu machen, werde ich mich dafür entscheiden, ihn so zu belassen, wie er ist, und effizienter zu arbeiten.

+2

Vielleicht benutzerdefinierte Attribute (https://msdn.microsoft.com/en-us/library/84c42s56.aspx) um ein Mapping zu definieren ist der Schlüssel? – ViRuSTriNiTy

+0

Nicht sicher, warum das helfen würde. Ich weiß, was der 'TDto'-Typ ist, wie es in der 'IRequest ' auf der Anfrage-Klasse ist. Es ist nur so, wie ich es rauskriege. – Nick

+0

Den Typ zu erhalten ist einfach: 'typeof (TOut) .GenericTypeArguments [0]' (Sie möchten wahrscheinlich eine Basis-IRequest-Schnittstelle erstellen, so dass Sie zumindest 'TOut' darauf beschränken können). Aber Sie können es nicht statisch übergeben (Sie können die Methode durch Reflexion aufrufen, aber das ist ziemlich hässlich). Ich denke, dass Automapper einige Überladungen hat, um den Typ zur Laufzeit zu übergeben. –

Antwort

1

Sie können es so machen, Erweiterungsmethoden zu missbrauchen:

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

} 

Dann können Sie es wie folgt aufrufen können:

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

Siehe here

+0

Markieren Sie dies als die Antwort, als das wäre was ich gegangen wäre. Ich verwende eine Instanz von AutoMapper in der Map-Funktion, die diese Option zu sehr verlangsamt. Die Antwort verwendet jedoch keine Reflexion, noch ist sie brüchig oder hässlich. – Nick

2

Generics sind für die Kompilierzeit gedacht, nicht für die Laufzeit. Wenn Sie es zur Laufzeit benötigen würden Sie Reflexion verwenden und so etwas zu tun:

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

Das ist ziemlich hässlich, vielleicht gibt es einen anderen Ansatz zu dem, was Sie erreichen wollen?

+0

Wie erhalten Sie die 'Data.GetType()'? Der TOut kennt die 'IRequest <>' Schnittstelle für diese Methode nicht. Ansonsten könnte das einfach funktionieren, wenn auch auf hässliche Weise. – Nick

+0

@Nick, dieser Code wird nicht funktionieren, da die generischen Parameter nicht so eingegrenzt sind, dass der Compiler weiß, was "new TOut(). Data" bedeutet. Es ist eine allgemeine Idee, aber es scheitert aufgrund des Constraint-Problems. – ViRuSTriNiTy

+0

@Nick Wir sollten in der Lage sein, es mit noch hässlicher Reflexion zu bekommen, siehe meine Bearbeitung. – EpicSam

Verwandte Themen