Espressione per chiamare un metodo su ciascuna proprietà di una classe

c# expression-trees lambda

Domanda

Voglio prendere una classe, scorrere le sue proprietà, ottenere il valore della proprietà e chiamare un metodo che trasmetta il valore della proprietà. Penso di poter ottenere i valori della proprietà, ma come appare il corpo dell'espressione lambda? Quale corpo è usato per chiamare un metodo su ogni proprietà?

Questo è quello che ho finora ...

Action<T> CreateExpression<T>( T obj )
{
 foreach( var property in typeof( T ).GetProperties() )
 {
  Expression value = Expression.Property( Expression.Constant( obj ), property );
  var method = Expression.Call( typeof( SomeType ), "SomeMethod", null, value );
 }

 // What expression body can be used that will call
 // all the method expressions for each property?
 var body = Expression...
 return Expression.Lambda<Action<T>>( body, ... ).Compile();
}

Risposta accettata

Dipende da alcune cose.

  • il metodo restituisce qualcosa? Expression in 3.5 non può eseguire più operazioni di "azione" separate (un corpo di istruzioni), ma puoi imbrogliare se puoi fare qualcosa con un'API fluente:

    SomeMethod(obj.Prop1).SomeMethod(obj.Prop2).SomeMethod(obj.Prop3);
    

    (magari usando i generici per renderlo più semplice)

  • hai accesso a 4.0? Nella versione 4.0 ci sono altri tipi di Expression consentono agli enti di certificazione e esattamente ciò che chiedi. Discuto alcuni esempi simili in un articolo qui (cercate Expression.Block , anche se questo è basato su una beta qualche tempo fa - potrebbe essere stato rinominato ora).

Alternativa; dal momento che si sta compilando a un delegato, si consideri che Action<T> è multicast; potresti creare una serie di operazioni semplici e combinarle nel delegato; questo funzionerebbe in 3,5; per esempio:

using System;
using System.Linq.Expressions;
static class SomeType
{
    static void SomeMethod<T>(T value)
    {
        Console.WriteLine(value);
    }
}
class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
}
static class Program
{
    static readonly Action<Customer> action = CreateAction<Customer>();
    static void Main()
    {
        Customer cust = new Customer { Id = 123, Name = "Abc" };
        action(cust);
    }
    static Action<T> CreateAction<T>()
    {
        Action<T> result = null;
        var param = Expression.Parameter(typeof(T), "obj");
        foreach (var property in typeof(T).GetProperties(
            BindingFlags.Instance | BindingFlags.Public))
        {
            if (property.GetIndexParameters().Length > 0) continue;
            var propVal = Expression.Property(param, property);
            var call = Expression.Call(typeof(SomeType), "SomeMethod", new Type[] {propVal.Type}, propVal);
            result += Expression.Lambda<Action<T>>(call, param).Compile();
        }
        return result;
    }
}

Risposta popolare

Non penso che sarà così facile usare Expressions, in .NET 3.5 almeno.

.NET 4 supporta un costrutto di blocco, credo.

Suggerisco piuttosto di usare Reflection.Emit.

Ecco un punto di partenza (per i campi ma può essere modificato facilmente):

internal static T CreateDelegate<T>(this DynamicMethod dm) where T : class
{
  return dm.CreateDelegate(typeof(T)) as T;
}

static Dictionary<Type, Func<object, Dictionary<string, object>>> fieldcache = 
  new Dictionary<Type, Func<object, Dictionary<string, object>>>();

static Dictionary<string, object> GetFields(object o)
{
  var t = o.GetType();

  Func<object, Dictionary<string, object>> getter;

  if (!fieldcache.TryGetValue(t, out getter))
  {
    var rettype = typeof(Dictionary<string, object>);

    var dm = new DynamicMethod(t.Name + ":GetFields", 
       rettype, new Type[] { typeof(object) }, t);

    var ilgen = dm.GetILGenerator();

    var instance = ilgen.DeclareLocal(t);
    var dict = ilgen.DeclareLocal(rettype);

    ilgen.Emit(OpCodes.Ldarg_0);
    ilgen.Emit(OpCodes.Castclass, t);
    ilgen.Emit(OpCodes.Stloc, instance);

    ilgen.Emit(OpCodes.Newobj, rettype.GetConstructor(Type.EmptyTypes));
    ilgen.Emit(OpCodes.Stloc, dict);

    var add = rettype.GetMethod("Add");

    foreach (var field in t.GetFields(
      BindingFlags.DeclaredOnly |
      BindingFlags.Instance |
      BindingFlags.Public |
      BindingFlags.NonPublic))
    {
      if (!field.FieldType.IsSubclassOf(typeof(Component)))
      {
        continue;
      }
      ilgen.Emit(OpCodes.Ldloc, dict);

      ilgen.Emit(OpCodes.Ldstr, field.Name);

      ilgen.Emit(OpCodes.Ldloc, instance);
      ilgen.Emit(OpCodes.Ldfld, field);
      ilgen.Emit(OpCodes.Castclass, typeof(object));

      ilgen.Emit(OpCodes.Callvirt, add);
    }

    ilgen.Emit(OpCodes.Ldloc, dict);
    ilgen.Emit(OpCodes.Ret);

    fieldcache[t] = getter = dm.CreateDelegate<Func<object, 
       Dictionary<string, object>>>();
  }

  return getter(o);
}


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é