Comment utiliser une expression <Func> définir une propriété imbriquée?

.net c# expression-trees reflection

Question

J'ai donc du code qui définit une propriété sur un objet. Ce code provient d'une classe de validation interne que nous utilisons dans les tests unitaires. Donc, le code peut être fourni quelque chose comme

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

Ce code est utilisé dans un environnement de type test unitaire, où nous pouvons ensuite effectuer des appels tels que

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

(code simplifié pour brièveté / complexité)

Cependant, maintenant, à cause de certaines modifications, il nous reste quelque chose comme:

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

Lorsque ce code est exécuté, nous obtenons l’erreur suivante:

L'objet ne correspond pas au type de cible.

Je soupçonne qu'il essaie d'appeler la propriété First sur BusinessObject au lieu de NameInfo . Comment puis-je modifier mon code pour gérer ce cas "imbriqué"?

Réponse populaire

Maintenant que vous nous avez donné un exemple, il est assez facile de le faire. Il serait inutile de compiler l'expression de quelque manière que ce soit, car nous ne pouvons pas la réutiliser, cela ne ferait que ralentir la méthode. Plus facile de marcher dans la "chaîne" des accesseurs et d'utiliser la réflexion pour accéder à leur valeur. La méthode que j'ai écrite prend en charge les champs (souvent utilisés en tant que champs en readonly ) et les propriétés.

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

Vous l'utilisez comme ceci:

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

Notez que SetDeepValue n'a pas besoin de, ni utilise, le targetObject , car il peut le découvrir dans la chaîne de getters:

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

Ici vous avez ()=>myCustomer .

Il aurait été nécessaire si vous appeliez était sous la forme

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

Je vais même vous donner une méthode qui utilise ce second format d' Expression :

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

Vous pouvez comparer les deux pour voir les différences.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi