2015-03-16 15 views
8

Quindi ho un codice che imposta una proprietà su un oggetto. Questo codice proviene da una classe di convalida interna che stiamo usando nei test unitari. Qualcosa di così il codice può essere fornito comeCome utilizzare un'espressione <Func> per impostare una proprietà nidificata?

private static void SetDeepValue(object targetObject, Expression<Func<string>> propertyToSet, object valueToSet) 
     { 
      var underlyingProperty = ((PropertyInfo)((MemberExpression)propertyToSet.Body).Member); 
      underlyingProperty.SetValue(targetObject, valueToSet); 
     } 

Questo codice viene utilizzato in un ambiente di tipo unit-test, dove possiamo quindi effettuare chiamate come

foreach (string currentTestCaseValue in TestCaseSets) 
{ 
    BusinessObject myCustomer = new BusinessObject(); 
    SetDeepValue(myCustomer,()=>myCustomer.FirstName,currentTestCaseValue); 
    ValidateBusinessRules(myCustomer); 
} 

(codice semplificato per brevità/complessità)

Tuttavia, ora, a causa di alcuni refactoring, ci ritroviamo con qualcosa di simile:

foreach (string currentTestCaseValue in TestCaseSets) 
    { 
     BusinessObject myCustomer = new BusinessObject(); 
     SetDeepValue(myCustomer,()=>myCustomer.NameInfo.First,currentTestCaseValue); 
     ValidateBusinessRules(myCustomer); 
    } 

Quando questo codice viene eseguito, si ottiene l'errore:

Object does not match target type.

Ho il sospetto che sta cercando di chiamare la proprietà First sul BusinessObject, al posto del NameInfo. Come posso modificare il mio codice per gestire questo caso "annidato"?

+1

Fornisci altre righe di programma e un esempio di propertyToValidate/objectUnderTest/eccessivamenteLargeNameValue. – xanatos

+0

Sono abbastanza sicuro che per gli oggetti nidificati, devi effettivamente ottenere l'oggetto e quindi eseguire un'altra chiamata '.SetValue()' su quell'oggetto – JNYRanger

+0

Vedere se http://stackoverflow.com/a/7723923/613130 è quello che ti serve ... Da un getter restituisce un setter d'azione – xanatos

risposta

3

Ecco come di solito si convertire la stringa "ColumnName1.ColumnName2" ad un'espressione lambda x => x.ColumnName1.ColumnName2:

Expression<Func<T, object>> ForNestedProperty(string columnName) 
{ 
    // x 
    ParameterExpression param = Expression.Parameter(typeof(T), "x"); 

    // x.ColumnName1.ColumnName2 
    Expression property = columnName.Split('.') 
            .Aggregate<string, Expression> 
            (param, (c, m) => Expression.Property(c, m)); 

    // x => x.ColumnName1.ColumnName2 
    Expression<Func<T, object>> lambda = Expression.Lambda<Func<T, object>>(
     Expression.Convert(property, typeof(object)), param); 
    return lambda; 
} 

(Copiato da here)

+2

Stavo per dire "dovresti dare credito all'autore originale", poi ho guardato un po' più attentamente ... – vcsjones

3

Ora che ci hai dato un esempio, è abbastanza facile per farlo. Sarebbe inutile compilare l'espressione in alcun modo, perché non possiamo riutilizzarla, quindi rallenterebbe solo il metodo. Più facile camminare sulla "catena" dei getter e utilizzare la riflessione per accedere al loro valore. Il metodo che ho scritto supporta entrambi i campi (spesso usati come campi readonly) e proprietà.

public static void SetDeepValue<T>(object notUsed, Expression<Func<T>> propertyToSet, T valueToSet) 
{ 
    List<MemberInfo> members = new List<MemberInfo>(); 

    Expression exp = propertyToSet.Body; 
    ConstantExpression ce = null; 

    // There is a chain of getters in propertyToSet, with at the 
    // beginning a ConstantExpression. We put the MemberInfo of 
    // these getters in members and the ConstantExpression in ce 

    while (exp != null) 
    { 
     MemberExpression mi = exp as MemberExpression; 

     if (mi != null) 
     { 
      members.Add(mi.Member); 
      exp = mi.Expression; 
     } 
     else 
     { 
      ce = exp as ConstantExpression; 

      if (ce == null) 
      { 
       // We support only a ConstantExpression at the base 
       // no function call like 
       //() => myfunc().A.B.C 
       throw new NotSupportedException(); 
      } 

      break; 
     } 
    } 

    if (members.Count == 0) 
    { 
     // We need at least a getter 
     throw new NotSupportedException(); 
    } 

    // Now we must walk the getters (excluding the last). 
    // From the ConstantValue ce we take the base object 
    object targetObject = ce.Value; 

    // We have to walk the getters from last (most inner) to second 
    // (the first one is the one we have to use as a setter) 
    for (int i = members.Count - 1; i >= 1; i--) 
    { 
     PropertyInfo pi = members[i] as PropertyInfo; 

     if (pi != null) 
     { 
      targetObject = pi.GetValue(targetObject); 
     } 
     else 
     { 
      FieldInfo fi = (FieldInfo)members[i]; 
      targetObject = fi.GetValue(targetObject); 
     } 
    } 

    // The first one is the getter we treat as a setter 
    { 
     PropertyInfo pi = members[0] as PropertyInfo; 

     if (pi != null) 
     { 
      pi.SetValue(targetObject, valueToSet); 
     } 
     else 
     { 
      FieldInfo fi = (FieldInfo)members[0]; 
      fi.SetValue(targetObject, valueToSet); 
     } 
    } 
} 

si utilizza in questo modo:

A a = new A(); 

SetDeepValue(a,() => a.B.C.Value, "Foo"); 

Si noti che il SetDeepValue non ha bisogno né usi, il targetObject, perché può scoprirlo nella catena di getter:

SetDeepValue(myCustomer,()=>myCustomer.FirstName, currentTestCaseValue); 

Qui hai ()=>myCustomer.

Sarebbe stato necessario se si chiama era in forma

SetDeepValue(myCustomer, x=>x.FirstName, currentTestCaseValue); 

Potrei anche darvi un metodo che utilizza questo secondo formato di Expression:

public static void SetDeepValue<TObject, T>(TObject target, Expression<Func<TObject, T>> propertyToSet, T valueToSet) 
{ 
    List<MemberInfo> members = new List<MemberInfo>(); 

    Expression exp = propertyToSet.Body; 

    // There is a chain of getters in propertyToSet, with at the 
    // beginning a ParameterExpression. We put the MemberInfo of 
    // these getters in members. We don't really need the 
    // ParameterExpression 

    while (exp != null) 
    { 
     MemberExpression mi = exp as MemberExpression; 

     if (mi != null) 
     { 
      members.Add(mi.Member); 
      exp = mi.Expression; 
     } 
     else 
     { 
      ParameterExpression pe = exp as ParameterExpression; 

      if (pe == null) 
      { 
       // We support only a ParameterExpression at the base 
       throw new NotSupportedException(); 
      } 

      break; 
     } 
    } 

    if (members.Count == 0) 
    { 
     // We need at least a getter 
     throw new NotSupportedException(); 
    } 

    // Now we must walk the getters (excluding the last). 
    object targetObject = target; 

    // We have to walk the getters from last (most inner) to second 
    // (the first one is the one we have to use as a setter) 
    for (int i = members.Count - 1; i >= 1; i--) 
    { 
     PropertyInfo pi = members[i] as PropertyInfo; 

     if (pi != null) 
     { 
      targetObject = pi.GetValue(targetObject); 
     } 
     else 
     { 
      FieldInfo fi = (FieldInfo)members[i]; 
      targetObject = fi.GetValue(targetObject); 
     } 
    } 

    // The first one is the getter we treat as a setter 
    { 
     PropertyInfo pi = members[0] as PropertyInfo; 

     if (pi != null) 
     { 
      pi.SetValue(targetObject, valueToSet); 
     } 
     else 
     { 
      FieldInfo fi = (FieldInfo)members[0]; 
      fi.SetValue(targetObject, valueToSet); 
     } 
    } 
} 

è possibile confrontare i due per vedere le differenze.

+0

Questo ha molto più senso, cercherò di capirmi e capire e poi provarlo oggi. Ci sono particolari libri/blog/risorse web che coprono davvero le espressioni in dettaglio che consiglieresti, a proposito? – GWLlosa

+0

@ GWLlosa Non lo so ... Li ho studiati alcuni anni fa e da allora non avevo bisogno di alcuna risorsa "di base" ... E non ricordo come li ho studiati. che non ho fatto niente di divertente con l'espressione qui. L'ho semplicemente passato indietro d. Se hai domande specifiche sugli alberi di espressione, qui su SO ci sono molti dadi di Expression-tree :-) – xanatos

Problemi correlati