Wie verwende ich einen Ausdruck? eine geschachtelte Eigenschaft festlegen?

.net c# expression-trees reflection

Frage

Also habe ich etwas Code, der eine Eigenschaft auf ein Objekt setzt. Dieser Code stammt aus einer internen Validierungsklasse, die wir in Komponententests verwenden. So kann der Code etwas wie geliefert werden

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

Dieser Code wird in einer Unit-Test-Umgebung verwendet, in der wir dann Anrufe tätigen können

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

(Code für Kürze / Komplexität vereinfacht)

Aufgrund einiger Refactorings bleibt uns jedoch etwas wie:

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

Wenn dieser Code ausgeführt wird, erhalten wir den Fehler:

Das Objekt stimmt nicht mit dem Zieltyp überein.

Ich vermute, dass es versucht, die First Eigenschaft für das BusinessObject anstelle von NameInfo . Wie kann ich meinen Code ändern, um diesen "verschachtelten" Fall zu bearbeiten?

Beliebte Antwort

Nun, da Sie uns ein Beispiel gegeben haben, ist es ziemlich einfach. Es wäre sinnlos, den Ausdruck in irgendeiner Weise zu kompilieren, weil wir ihn nicht wiederverwenden können, so dass es die Methode nur verlangsamen würde. Es ist einfacher, die "Kette" der Getter zu durchlaufen und Reflektionen zu nutzen, um auf ihren Wert zuzugreifen. Die Methode, die ich geschrieben habe, unterstützt sowohl Felder (die oft als readonly Felder verwendet werden) als auch Eigenschaften.

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

Du benutzt es so:

A a = new A();

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

Beachten Sie, dass der SetDeepValue nicht braucht, noch verwendet, der targetObject , weil sie es in der Kette von Getter entdecken können:

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

Hier haben Sie ()=>myCustomer .

Es wäre notwendig gewesen, wenn du in Form gerufen hättest

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

Ich gebe Ihnen sogar eine Methode, die dieses zweite 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);
        }
    }
}

Sie können die beiden vergleichen, um die Unterschiede zu sehen.



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum