Ruft den Parameterwert von einem Linq-Ausdruck ab

c# expression-trees linq

Frage

Ich habe folgende Klasse

public class MyClass
{
    public bool Delete(Product product)
    {
        // some code.
    }
}

Jetzt habe ich eine Hilfsklasse, die so aussieht

public class Helper<T, TResult>
{

    public Type Type;
    public string Method;
    public Type[] ArgTypes;
    public object[] ArgValues;

    public Helper(Expression<Func<T, TResult>> expression)
    {
        var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;

        this.Type = typeof(T);
        this.Method = body.Method.Name;
        this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray();
        this.ArgValues = ???
    }
}

Die Idee ist, diesen Code irgendwo zu verwenden:

// I am returning a helper somewhere
public Helper<T> GetMethod<T>()
{
    var product = GetProduct(1);
    return new Helper<MyClass>(x => x.Delete(product));
}

// some other class decides, when to execute the helper 
// Invoker already exists and is responsible for executing the method
// that is the main reason I don't just comile and execute my Expression
public bool ExecuteMethod<T>(Helper<T> helper)
{
    var instance = new MyClass();
    var Invoker = new Invoker(helper.Type, helper.Method, helper.ArgTypes, helper.ArgValues);
    return (bool)Invoker.Invoke(instance);
}

Der Punkt, an dem ich feststecke, ist, wie man die Argumente aus dem Ausdruck selbst extrahiert.

Ich habe diesen Weg gefunden

((ConstantExpression)((MemberExpression)body.Arguments[0]).Expression).Value

das scheint ein Objekttyp mit einem Feld "Produkt" zu sein, aber ich glaube, dass es eine einfachere Lösung geben muss.

Irgendwelche Vorschläge.

Aktualisieren

Um das klarzustellen, habe ich meinen Code entsprechend dem geändert, was ich erreichen möchte. In meiner echten Wortanwendung habe ich bereits eine Klasse, die das Gleiche tut, aber ohne einen Ausdrucksbaum:

var helper = new Helper(typeof(MyClass), "Delete", 
    new Type[] { typeof(Product) }, new object[] {product}));

Der Hauptgrund für meinen Helper<T> ist die Compile-Time-Prüfung, wenn die Methodensignatur gültig ist.

Update 2

Dies ist meine aktuelle Implementierung, gibt es einen besseren Weg, auf die Werte zuzugreifen, ohne die Reflektion zu benutzen?

public Helper(Expression<Func<T, TResult>> expression)
{
    var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;

    this.Type = typeof(T);
    this.Method = body.Method.Name;
    this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray();

    var values = new List<object>();
    foreach(var arg in body.Arguments)
    {
        values.Add(
            (((ConstantExpression)exp.Expression).Value).GetType()
                .GetField(exp.Member.Name)
                .GetValue(((ConstantExpression)exp.Expression).Value);
        );
    }
    this.ArgValues = values.ToArray();
}

Akzeptierte Antwort

Diese Methode funktioniert ziemlich gut. Es gibt die Argumenttypen und -werte für einen Ausdruck> zurück

    private static KeyValuePair<Type, object>[] ResolveArgs<T>(Expression<Func<T, object>> expression)
    {
        var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
        var values = new List<KeyValuePair<Type, object>>();

        foreach (var argument in body.Arguments)
        {
            var exp = ResolveMemberExpression(argument);
            var type = argument.Type;

            var value = GetValue(exp);

            values.Add(new KeyValuePair<Type, object>(type, value));
        }

        return values.ToArray();
    }

    public static MemberExpression ResolveMemberExpression(Expression expression)
    {

        if (expression is MemberExpression)
        {
            return (MemberExpression)expression;
        }
        else if (expression is UnaryExpression)
        {
            // if casting is involved, Expression is not x => x.FieldName but x => Convert(x.Fieldname)
            return (MemberExpression)((UnaryExpression)expression).Operand;
        }
        else
        {
            throw new NotSupportedException(expression.ToString());
        }
    }

    private static object GetValue(MemberExpression exp)
    {
        // expression is ConstantExpression or FieldExpression
        if (exp.Expression is ConstantExpression)
        {
            return (((ConstantExpression)exp.Expression).Value)
                    .GetType()
                    .GetField(exp.Member.Name)
                    .GetValue(((ConstantExpression)exp.Expression).Value);    
        }
        else if (exp.Expression is MemberExpression)
        {
            return GetValue((MemberExpression)exp.Expression);
        }
        else
        {
            throw new NotImplementedException();
        }
    }

Beliebte Antwort

Sie können den Argumentausdruck kompilieren und anschließend aufrufen, um den Wert zu berechnen:

var values = new List<object>();
foreach(var arg in body.Arguments)
{
    var value = Expression.Lambda(argument).Compile().DynamicInvoke();
    values.Add(value);
}
this.ArgValues = values.ToArray();


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow