2010-07-30 22 views
14

Questa è una domanda teorica, ho già trovato una soluzione al mio problema che mi ha portato su un percorso diverso, ma penso che la domanda sia ancora potenzialmente interessante.Come posso passare una proprietà come delegato?

Posso passare le proprietà dell'oggetto come delegati nello stesso modo in cui posso con i metodi? Ad esempio:

Diciamo che ho caricato un lettore di dati con i dati e che il valore di ogni campo deve essere passato in proprietà di tipi diversi che sono stati controllati per DBNull. Se il tentativo di ottenere un singolo campo, potrei scrivere qualcosa di simile:

if(!rdr["field1"].Equals(DBNull.Value)) myClass.Property1 = rdr["field1"]; 

Ma se devo dire 100 campi, che diventa ingombrante molto rapidamente. Ci sono un paio di modi in cui una chiamata a fare questo potrebbe essere bello:

myClass.Property = GetDefaultOrValue<string>(rdr["field1"]); //Which incidentally is the route I took 

Il che potrebbe anche guardare bello come un metodo di estensione:

myClass.Property = rdr["field1"].GetDefaultOrValue<string>(); 

Oppure:

SetPropertyFromDbValue<string>(myClass.Property1, rdr["field1"]); //Which is the one that I'm interested in on this theoretical level 

Nel in secondo luogo, la proprietà dovrebbe essere passata come delegato per impostarla.

Quindi la domanda è in due parti:

  1. è possibile?
  2. Che aspetto avrebbe?

[Dato che questo è solo teorica, risposte in VB o C# sono ugualmente accettabile per me]

Edit: Ci sono alcune risposte chiazza di petrolio qui. Ringrazia tutti.

risposta

11

(Aggiunta di una seconda risposta, perché è su un approccio completamente diverso)

Per affrontare il problema originale, che è più su che vogliono una bella API per la mappatura valori denominati in un DataReader per immobili in vostro oggetto, in considerazione System.ComponentModel.TypeDescriptor - un'alternativa spesso trascurata a fare il dirtywork riflessivo voi stessi.

Ecco un frammento utile:

var properties = TypeDescriptor.GetProperties(myObject) 
    .Cast<PropertyDescriptor>() 
    .ToDictionary(pr => pr.Name); 

che crea un dizionario dei propertydescriptors del vostro oggetto.

ora posso fare questo:

properties["Property1"].SetValue(myObject, rdr["item1"]); 

PropertyDescriptor 's metodo SetValue (a differenza System.Reflection.PropertyInfo' s equivalente) farà la conversione del tipo per voi - analizzare le stringhe come interi, e così via.

Cosa c'è di utile di questo è si può immaginare un approccio attributo-driven per l'iterazione attraverso che le proprietà di raccolta (PropertyDescriptor ha una proprietà Attributes per consentire di ottenere eventuali attributi personalizzati che sono stati aggiunti alla proprietà) per capire quale valore nel datareader da usare; o con un metodo che riceve un dizionario di nome proprietà - mapping di nomi di colonne che scorre e esegue tutti questi set per te.

Sospetto che un approccio come questo possa darti la scorciatoia API di cui hai bisogno in un modo in cui l'inganno riflettente espressione lambda - in questo caso - non lo farà.

+0

Anch'io amo le implicazioni di questo. Bella risposta. – BenAlabaster

6

No, non c'è nulla di simile alle conversioni dei gruppi di metodi per le proprietà. Il meglio che puoi fare è usare un'espressione lambda per formare una Func<string> (per un getter) o un Action<string> (per un setter):

SetPropertyFromDbValue<string>(value => myClass.Property1 = value, 
           rdr["field1"]); 
+1

Interessante, il lambda non era passato per la testa, naturalmente, che sarebbe più ingombrante della mia prima dichiarazione in questo scenario specifico. Comunque è interessante, mi chiedo se questo potrebbe essere fatto un passo avanti in qualche modo per avvolgerlo ... o forse il mio cervello si sta solo allontanando da me: P – BenAlabaster

+0

I tipi generici possono essere passati in delegati? – BenAlabaster

+0

@BenAlabaster: non sono sicuro di cosa intendi con l'ultima domanda ... –

2

Vale la pena menzionare è possibile farlo con una certa riflessione inganno .. qualcosa come ...

public static void LoadFromReader<T>(this object source, SqlDataReader reader, string propertyName, string fieldName) 
    { 
     //Should check for nulls.. 
     Type t = source.GetType(); 
     PropertyInfo pi = t.GetProperty(propertyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); 
     object val = reader[fieldName]; 
     if (val == DBNull.Value) 
     { 
      val = default(T); 
     } 
     //Try to change to same type as property... 
     val = Convert.ChangeType(val, pi.PropertyType); 
     pi.SetValue(source, val, null); 
    } 

poi

myClass.LoadFromReader<string>(reader,"Property1","field1"); 
+0

Avevo pensato al riflesso, ma è una di quelle aree che sono sempre interessato a trascinare prestazioni in calo. Di conseguenza, è una nicchia nella quale passo solo nelle aree della mia applicazione che è meno probabile che abbia un impatto. Nelle aree in cui sto caricando centinaia di oggetti o analizzando dati in centinaia di campi, sarei preoccupato che ciò impedirebbe troppo le prestazioni. – BenAlabaster

+0

Ti ho dato +1 anche perché risponde alla domanda, non era proprio in linea con quello che stavo cercando (che non ho specificato nella domanda). – BenAlabaster

14

mi piace usare alberi di espressione per risolvere questo problema. Ogni volta che si dispone di un metodo in cui si desidera prendere un "delegato di proprietà", utilizzare il tipo di parametro Expression<Func<T, TPropertyType>>. Ad esempio:

public void SetPropertyFromDbValue<T, TProperty>(
    T obj, 
    Expression<Func<T, TProperty>> expression, 
    TProperty value 
) 
{ 
    MemberExpression member = (MemberExpression)expression.Body; 
    PropertyInfo property = (PropertyInfo)member.Member; 
    property.SetValue(obj, value, null); 
} 

La cosa bella di questo è che anche la sintassi ha lo stesso aspetto.

public TProperty GetPropertyFromDbValue<T, TProperty>(
    T obj, 
    Expression<Func<T, TProperty>> expression 
) 
{ 
    MemberExpression member = (MemberExpression)expression.Body; 
    PropertyInfo property = (PropertyInfo)member.Member; 
    return (TProperty)property.GetValue(obj, null); 
} 

Oppure, se ti senti pigro:

public TProperty GetPropertyFromDbValue<T, TProperty>(
    T obj, 
    Expression<Func<T, TProperty>> expression 
) 
{ 
    return expression.Compile()(obj); 
} 

Invocazione sarà simile:

SetPropertyFromDbValue(myClass, o => o.Property1, reader["field1"]); 
GetPropertyFromDbValue(myClass, o => o.Property1); 
+0

Questo è fantastico, ero nel bel mezzo di avere un pensiero simile ispirato alla risposta di @ JonSkeet. Sto pensando di semplificarlo un po ', ma sto provando a pensarlo tra mille altri ogni giorno I.T. problemi ... come fai tu: P – BenAlabaster

+0

+1 da me. Questo non si compilerebbe quindi ho modificato le correzioni. Spero di aver ragione, per favore perdonami se no. Inoltre, questo rende un ottimo set di metodi di estensione, non ultimo 'PropertyInfo statico pubblico GetPropertyInfo (questa espressione > espressione)' che sono stato in grado di utilizzare come una delle basi per un'altra funzione di libreria per ottenere, con generici, la lunghezza massima di un campo di stringa da Linq a Sql. Grazie! –

+1

@StephenKennedy, grazie, bella cattura! SO ha bisogno di un pulsante di compilazione. ;) –

6

Ignorando se questo è utile in circostanze specifiche (dove penso che l'approccio hai preso i lavori bene), la tua domanda è "c'è un modo per convertire una proprietà in un delegato".

Beh, il tipo di potrebbe essere.

Ogni proprietà effettivamente (dietro le quinte) consiste di uno o due metodi: un metodo set e/o un metodo get. E puoi - se riesci a prendere possesso di questi metodi - fai diventare i delegati che li avvolgono.

Per esempio, una volta che hai in mano un oggetto che rappresenta System.Reflection.PropertyInfo una proprietà di tipo TProp su un oggetto di tipo TObj, possiamo creare un Action<TObj,TProp> (vale a dire, un delegato che prende un oggetto su cui impostare il proprietà e un valore per impostarlo) che avvolge quel metodo setter come segue:

Delegate.CreateDelegate(typeof (Action<TObj, TProp>), propertyInfo.GetSetMethod()) 

Oppure si può creare un Action<TProp> che avvolge il setter su una specifica istanza di TObj come questo:

Delegate.CreateDelegate(typeof (Action<TProp>), instance, propertyInfo.GetSetMethod()) 

Possiamo avvolgere quel piccolo sacco utilizzando un metodo static reflection estensione:

public static Action<T> GetPropertySetter<TObject, T>(this TObject instance, Expression<Func<TObject, T>> propAccessExpression) 
{ 
    var memberExpression = propAccessExpression.Body as MemberExpression; 
    if (memberExpression == null) throw new ArgumentException("Lambda must be a simple property access", "propAccessExpression"); 

    var accessedMember = memberExpression.Member as PropertyInfo; 
    if (accessedMember == null) throw new ArgumentException("Lambda must be a simple property access", "propAccessExpression"); 

    var setter = accessedMember.GetSetMethod(); 

    return (Action<T>) Delegate.CreateDelegate(typeof(Action<T>), instance, setter); 
} 

e ora posso ottenere una sospensione di un delegato 'setter' per una proprietà su un oggetto come questo:

MyClass myObject = new MyClass(); 
Action<string> setter = myObject.GetPropertySetter(o => o.Property1); 

Questo è fortemente digitato, in base al tipo di proprietà stessa, quindi è robusto di fronte al refactoring e al tipo compilato in fase di digitazione.

Naturalmente, nel tuo caso, vuoi essere in grado di impostare la tua proprietà usando un oggetto possibilmente nullo, quindi un wrapper fortemente tipizzato attorno al setter non è l'intera soluzione - ma ti dà qualcosa da passare al tuo metodo SetPropertyFromDbValue.

+0

Mi piace parecchio. Grazie. – BenAlabaster

Problemi correlati