2009-08-27 9 views
12

Odio avere un sacco di metodi "sinistra/destra". Ogni volta che una proprietà viene aggiunta o rimossa, devo correggere ogni metodo. E il codice stesso sembra ... sbagliato.Come utilizzare la riflessione per semplificare costruttori e confronti?

public Foo(Foo other) 
{ 
    this.Bar = other.Bar; 
    this.Baz = other.Baz; 
    this.Lur = other.Lur; 
    this.Qux = other.Qux; 
    this.Xyzzy= other.Xyzzy; 
} 

In realtà questo è solo un loop srotolato che consente di scorrere le proprietà, copiandoli tra gli oggetti. Quindi, perché non essere onesti su questo fatto? Riflessione in soccorso!

public Foo(IFoo other) 
{ 
    foreach (var property in typeof(IFoo).GetProperties()) 
    { 
     property.SetValue(this, property.GetValue(other, null), null); 
    } 
} 

posso solo cercare di forzare un paradigma che ho imparato da Lua in C#, ma questo particolare esempio non sembra troppo puzzolente a me. Da qui, ho iniziato a fare alcune cose più complesse che erano sensibili all'ordine dei campi. Ad esempio, piuttosto che avere una pila di praticamente identiche if dichiarazioni per comporre una stringa dai campi, ho appena scorrere su di loro nell'ordine desiderato:

public override string ToString() 
{ 
    var toJoin = new List<string>(); 
    foreach (var property in tostringFields) 
    { 
     object value = property.GetValue(this, null); 
     if (value != null) 
      toJoin.Add(value.ToString()); 
    } 
    return string.Join(" ", toJoin.ToArray()); 
} 
private static readonly PropertyInfo[] tostringFields = 
{ 
    typeof(IFoo).GetProperty("Bar"), 
    typeof(IFoo).GetProperty("Baz"), 
    typeof(IFoo).GetProperty("Lur"), 
    typeof(IFoo).GetProperty("Qux"), 
    typeof(IFoo).GetProperty("Xyzzy"), 
}; 

Così ora ho l'iterabilità che volevo, ma ho ancora ho stack di codice che rispecchiano ogni proprietà a cui sono interessato (lo sto facendo anche per CompareTo, usando un diverso insieme di proprietà in un ordine diverso). Peggio ancora, è la perdita della tipizzazione forte. Questo sta davvero iniziando a sentire l'odore.

E che dire degli attributi su ogni proprietà per definire l'ordine? Ho iniziato su questa strada e in effetti ha funzionato bene, ma ha semplicemente reso tutto gonfio. Funziona alla grande semanticamente, ma sono sempre cauto nell'usare funzioni avanzate solo perché sono "pulite". Sta usando il riflesso in questo modo eccessivo? C'è qualche altra soluzione al problema del codice sinistro/destro che mi manca?

+0

i'dd aggiungere un tag di lingua per ottenere più spettatori – Toad

+0

Tenete a mente, la riflessione viene sempre con un calo di prestazioni in C#. Quanto spesso manipolerai Foos come questo? Una soluzione ibrida potrebbe essere quella di utilizzare Reflection.Emit e memorizzare nella cache il codice risultante: avvio più lento, esecuzione rapida. – zildjohn01

+1

C# 4.0 viene fornito con compilatore incorporato, che può essere utile per generare codice e usarlo, per ora abbiamo anche problemi simili ma abbiamo progettato una soluzione utilizzando le strutture di classi XML e usiamo il nostro metodo di generazione del codice per creare classi che genera molti altri metodi che dipendono dai nomi dei memebers, è fondamentalmente un ORML ma fa tutto automaticamente. –

risposta

5

L'uso della riflessione in sé e per sé non è male, ma si verificherà un calo di prestazioni, soprattutto se lo si fa in modo ricorsivo.

Non sono un fan dei costruttori di copia hardcoded sia perché gli sviluppatori dimenticano di aggiornarli quando aggiungono nuove proprietà a una classe.

Esistono altri modi per realizzare ciò che si desidera, tra cui Marc Gravells Hyper Property Descriptor o se si desidera conoscere alcuni IL e OPCodes è possibile utilizzare System.Reflection.Emit o anche Cecil from Mono.

Ecco un esempio di utilizzo di Hyper proprietà descrittore che si può eventualmente su misura per le vostre esigenze:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using Hyper.ComponentModel; 
namespace Test { 
    class Person { 
     public int Id { get; set; } 
     public string Name { get; set; } 
    } 
    class Program { 
     static void Main() { 
      HyperTypeDescriptionProvider.Add(typeof(Person)); 
      var properties = new Dictionary<string, object> { { "Id", 10 }, { "Name", "Fred Flintstone" } }; 
      Person person = new Person(); 
      DynamicUpdate(person, properties); 
      Console.WriteLine("Id: {0}; Name: {1}", person.Id, person.Name); 
      Console.ReadKey(); 
     } 
     public static void DynamicUpdate<T>(T entity, Dictionary<string, object> { 
      foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(typeof(T))) 
       if (properties.ContainsKey(propertyDescriptor.Name)) 
        propertyDescriptor.SetValue(entity, properties[propertyDescriptor.Name]); 
     } 
    } 
} 

Se si decide di continuare ad usare la riflessione, è possibile ridurre le prestazioni colpito da caching le chiamate a GetProperties () in questo modo:

public Foo(IFoo other) { 
    foreach (var property in MyCacheProvider.GetProperties<IFoo>()) 
     property.SetValue(this, property.GetValue(other, null), null); 
} 
+3

Type.GetProperties esegue già il proprio caching (ora la prima chiamata e le successive chiamate). Il vero colpo di riflessione delle prestazioni è la proprietà. Chiamata SetValue, che puoi mitigare con la generazione di MSIL, ecc. –

+0

@Rob Fonseca-Ensor Grazie, non lo sapevo. – grenade

+0

Hmm ... se il risultato della prestazione è nell'impostare i valori, questa probabilmente non è la strada da percorrere. Cercare di occuparsi di tutte quelle cose di basso livello sarebbe sicuramente eccessivo. Grazie per l'input. – Cogwheel

2

IMHO, la riflessione è una caratteristica molto potente di C#, ma che è molto suscettibile di provocare un codice gonfio, e che aggiunge molto alla curva di apprendimento del codice e riduce manutenibilità. Avrai maggiori probabilità di commettere errori (una volta che il refactoring di base potrebbe portare a errori) e più paura di cambiare il nome di qualsiasi proprietà (se trovi un nome migliore) o cose del genere.

Personalmente ho un codice con un problema simile, e ho avuto la stessa idea di aggiungere attributi per mantenere l'ordine e così via. Ma il mio team (incluso me) ha pensato che fosse meglio perdere un po 'di tempo cambiando il design per non aver bisogno Questo. Forse questo problema è causato da un cattivo design (beh, era nel mio caso, ma non posso dire lo stesso del tuo).

+0

Non sono sicuro di come sarebbe più soggetto ad errori quando si cambiano le proprietà poiché l'intero punto è eliminare la necessità di fare riferimento ai nomi delle proprietà direttamente nel codice. In questo momento sto solo creando un modello di dominio che verrà mappato su un database, quindi l'aggiunta/rimozione/ridenominazione delle proprietà sarà praticamente non-stop durante lo sviluppo (soprattutto perché sto provando TDD per la prima volta) – Cogwheel

+0

Hai "typeof (IFoo) .GetProperty (" Bar ")" nel tuo codice, che rimanda direttamente ai nomi delle proprietà –

+0

Quindi gli ultimi due paragrafi nella mia domanda;) – Cogwheel

3

So che c'è già una risposta a questo, ma volevo sottolineare che esiste una libreria che combina alcune delle strategie di mitigazione per l'impatto sulle prestazioni che alcune persone hanno discusso.

La libreria è denominata AutoMapper e esegue il mapping da un oggetto a un altro e lo fa creando dinamicamente un assembly IL al volo. Questo assicura che diverso da un primo colpo volta, si ottiene prestazioni superiori e il tuo codice sarebbe molto più semplice:

public Foo(Foo other) 
{ 
    Mapper.Map(other, this); 
} 

Questo tende a funzionare grande e ha il vantaggio di non essere inventato qui, che io sono un fan di.

Ho eseguito alcuni test delle prestazioni e dopo il primo hit di 20 ms (ancora piuttosto veloce) era circa il più vicino possibile a 0. Molto impressionante.

Spero che questo aiuti qualcuno.

+0

Oppure puoi usare emitmapper che è ancora più veloce. –

+0

Non credo che emitmapper fosse disponibile nel '09 quando ho inviato questo :) –

+0

Bene ... non è mai tardi per migliorare la risposta :) –

2

Il problema di base è che si sta tentando di utilizzare un linguaggio tipizzato in modo statico come un tipo tipizzato dinamicamente.

Non c'è davvero bisogno di niente di speciale. Se si desidera poter iterare le proprietà, è possibile utilizzare una mappa <> come archivio di supporto per tutte le proprietà della classe.

Per coincidenza, questo è esattamente il modo in cui il wizard di progetto VS implora le impostazioni dell'applicazione. (Vedi System.Configuration.ApplicationSettingsBase) E 'anche molto 'lua-like'

public bool ConfirmSync { 
     get { 
      return ((bool)(this["ConfirmSync"])); 
     } 
     set { 
      this["ConfirmSync"] = value; 
     } 
    } 
+0

Bello! Strategia molto interessante. Ciò consentirebbe di eseguire il looping sul backing store e copiare tutto in un ciclo molto semplice. Molto creativo. –

+0

Grazie per il suggerimento. Probabilmente finirò per usarlo a un certo punto, ma non è proprio la soluzione giusta per il mio compito attuale. – Cogwheel