Come cambiare un tipo in un albero di espressioni?

c# expression-trees

Domanda

Ho un metodo come questo:

private bool Method_1(Expression<Func<IPerson, bool>> expression)
{
    /* Some code that will call Method_2 */
}

In questo metodo voglio cambiare il tipo di IPerson in un altro tipo. Voglio chiamare un altro metodo che assomiglia a questo:

private bool Method_2(Expression<Func<PersonData, bool>> expression)
{
    /* Some code */
}

Così, in method_1 ho bisogno di cambiare IPerson a PersonData . Come posso fare questo?

Modificare:

Quando chiamo: Method_1(p => p.Id == 1) Voglio "salvare" la condizione ( p.Id == 1 ) ma voglio eseguire questa condizione su un altro tipo, cioè IPerson . Quindi, ho bisogno di modificare l'espressione o creare una nuova espressione con IPerson

MODIFICA II:

Per coloro che sono interessati, questa è (per ora) la mia soluzione:

private class CustomExpressionVisitor<T> : ExpressionVisitor
{
    ParameterExpression _parameter;

    public CustomExpressionVisitor(ParameterExpression parameter)
    {
        _parameter = parameter;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return _parameter;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Member.MemberType == System.Reflection.MemberTypes.Property)
        {
            MemberExpression memberExpression = null;
            var memberName = node.Member.Name;
            var otherMember = typeof(T).GetProperty(memberName);
            memberExpression = Expression.Property(Visit(node.Expression), otherMember);
            return memberExpression;
        }
        else 
        {
            return base.VisitMember(node);
        }
    }
}

E questo è il modo in cui lo uso:

public virtual bool Exists(Expression<Func<Dto, bool>> expression)
{
    var param = Expression.Parameter(typeof(I));
    var result = new CustomExpressionVisitor<I>(param).Visit(expression.Body);
    Expression<Func<I, bool>> lambda = Expression.Lambda<Func<I, bool>>(result, param);

    bool exists = _repository.Exists(lambda);
    return exists;
}

Risposta accettata

È facile se si utilizza .net 4 (aggiornamento: come indicato nel commento ExpressionVisitor stato aggiunto nella versione 4 non 4.5) richiederebbe alcuni googling per i framework precedenti:

Ci sono alcune ipotesi, ma penso che siano valide per il tuo scenario DTO e Entity: le proprietà cui si accede devono corrispondere.

class PersonData
{
    public bool Prop { get; set; }
}

interface IPerson 
{
    bool Prop { get; set; }
}

In .net 4 è definita la classe ExpressionVisitor che rende questo molto più semplice se si utilizza uno più vecchio, quindi è necessario scrivere o trovare l'implementazione di esso:

class Visitor<T> : ExpressionVisitor
{
    ParameterExpression _parameter;

    //there must be only one instance of parameter expression for each parameter 
    //there is one so one passed here
    public Visitor(ParameterExpression parameter)
    {
        _parameter = parameter;
    }

    //this method replaces original parameter with given in constructor
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return _parameter;
    }

    //this one is required because PersonData does not implement IPerson and it finds
    //property in PersonData with the same name as the one referenced in expression 
    //and declared on IPerson
    protected override Expression VisitMember(MemberExpression node)
    {
        //only properties are allowed if you use fields then you need to extend
        // this method to handle them
        if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
            throw new NotImplementedException();

        //name of a member referenced in original expression in your 
        //sample Id in mine Prop
        var memberName = node.Member.Name;
        //find property on type T (=PersonData) by name
        var otherMember = typeof(T).GetProperty(memberName);
        //visit left side of this expression p.Id this would be p
        var inner = Visit(node.Expression);
        return Expression.Property(inner, otherMember);
    }
}

Verifica teorica:

class Program
{
   static void Main()
    {
        //sample expression
        Expression<Func<IPerson, bool>> expression = x => x.Prop;

        //parameter that will be used in generated expression
        var param = Expression.Parameter(typeof(PersonData));
        //visiting body of original expression that gives us body of the new expression
        var body = new Visitor<PersonData>(param).Visit(expression.Body);
        //generating lambda expression form body and parameter 
        //notice that this is what you need to invoke the Method_2
        Expression<Func<PersonData, bool>> lambda = Expression.Lambda<Func<PersonData, bool>>(body, param);
        //compilation and execution of generated method just to prove that it works
        var boolValue = lambda.Compile()(new PersonData());
    }
}

Si noti che questo funzionerà per le espressioni semplici. Se è necessario gestire x.Prop.Prop1 < 3 è necessario estendere ulteriormente.



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché