2011-10-11 25 views
8

İfadelerime yeni bir şey veriyorum ve ifademi dönüştürmenin herhangi bir şekilde mümkün olup olmadığını bilmek istiyorumİfade <Func <TModel, dize >> - İfade <Eylem <TModel>> "Düzenleyici" - "Ayarlayıcı"

benim tModel tipi Müşteri olan bu örnekte diyelim ve bunun gibi bir yere atanır:

Expression<Func<TModel, string>> getvalueexpression = customer =>customer.Name 

bir şey Yani kısacası

Expression<Action<TModel,string>> setvalueexpression = [PSEUDOCODE] getvalueexpression = input 
Action<TModel,string> Setter = setvalueexpression.Compile(); 
Setter(mycustomer,value); 

gibi, ben her nasılsa inşa etmek istiyor ve derlemek getter ifadem tarafından belirtilen müşteri adını belirli bir değere ayarlayan bir ifade.

+0

Herhangi bir alıcı yoksa (yani, yalnızca bir düzenleyici varsa) ne yapılmalıdır? – Joey

+0

Burada genel bir çözüm oluşturmuyorum. Bunu kullanmayı planladığım özellikler acımasız alıcılara ve ayarlayıcılara sahip olacak, sadece Setter –

+2

'u nasıl kurtaracağınızı merak ediyorum. Bu soruya ilk cevap formunu gözden geçirin. http://stackoverflow.com/questions/2823236/creating-a-property-setter-delegate – baalazamon

cevap

10

Değiştirilmiş biçim. Bu sınıf, :-) etrafında bulabileceğiniz diğerlerinden çok daha iyidir. Bunun nedeni, bu sürümün doğrudan özellikleri (p => p.B) (herkes gibi :-)), iç içe geçmiş özellikleri (p => p.B.C.D), alanları (hem "terminal" hem de ") desteklemesidir. ortada ",hem de B ve D alanları olabilir) ve türlerin" iç "döküm (yani p => ((BType)p.B).C.D ve p => (p.B as BType).C.D). Desteklenmeyen tek şey" terminal "öğesinin dökümünü (yani p => (object)p.B) .

jeneratör iki "codepaths" vardır: "yuvalanmış" ifadeleri basit ifadeler (p => p.B) ve kod .NET 4.0 (yani Expression.Assign ifade türü olan) için varyantları vardır bazı kriterler itibaren. benim en hızlı delegeler şunlardır: "Basit" Delegate.CreateDelegate özellikleri, alanlar için Expression.Assign ve alanlar için "basit" FieldSetter (bu alanlar için yalnızca Expression.Assign'dan biraz daha yavaştır). Yani .NET 4.0 altında 3.5 olarak işaretlenmiş tüm kodları almalısınız.

Kodun bir kısmı benim değil. İlk (basit) versiyonu, Fluent NHibernate koduna dayanıyordu (ancak yalnızca doğrudan özellikleri destekliyordu), bazı diğer parçalar ise How do I set a field value in an C# Expression tree? ve Assignment in .NET 3.5 expression trees kodlarına dayanıyordu.

public static class FluentTools 
{ 
    public static Action<T, TValue> GetterToSetter<T, TValue>(Expression<Func<T, TValue>> getter) 
    { 
     ParameterExpression parameter; 
     Expression instance; 
     MemberExpression propertyOrField; 

     GetMemberExpression(getter, out parameter, out instance, out propertyOrField); 

     // Very simple case: p => p.Property or p => p.Field 
     if (parameter == instance) 
     { 
      if (propertyOrField.Member.MemberType == MemberTypes.Property) 
      { 
       // This is FASTER than Expression trees! (5x on my benchmarks) but works only on properties 
       PropertyInfo property = propertyOrField.Member as PropertyInfo; 
       MethodInfo setter = property.GetSetMethod(); 
       var action = (Action<T, TValue>)Delegate.CreateDelegate(typeof(Action<T, TValue>), setter); 
       return action; 
      } 
      #region .NET 3.5 
      else // if (propertyOrField.Member.MemberType == MemberTypes.Field) 
      { 
       // 1.2x slower than 4.0 method, 5x faster than 3.5 method 
       FieldInfo field = propertyOrField.Member as FieldInfo; 
       var action = FieldSetter<T, TValue>(field); 
       return action; 
      } 
      #endregion 
     } 

     ParameterExpression value = Expression.Parameter(typeof(TValue), "val"); 

     Expression expr = null; 

     #region .NET 3.5 
     if (propertyOrField.Member.MemberType == MemberTypes.Property) 
     { 
      PropertyInfo property = propertyOrField.Member as PropertyInfo; 
      MethodInfo setter = property.GetSetMethod(); 
      expr = Expression.Call(instance, setter, value); 
     } 
     else // if (propertyOrField.Member.MemberType == MemberTypes.Field) 
     { 
      expr = FieldSetter(propertyOrField, value); 
     } 
     #endregion 

     //#region .NET 4.0 
     //// For field access it's 5x faster than the 3.5 method and 1.2x than "simple" method. For property access nearly same speed (1.1x faster). 
     //expr = Expression.Assign(propertyOrField, value); 
     //#endregion 

     return Expression.Lambda<Action<T, TValue>>(expr, parameter, value).Compile(); 
    } 

    private static void GetMemberExpression<T, U>(Expression<Func<T, U>> expression, out ParameterExpression parameter, out Expression instance, out MemberExpression propertyOrField) 
    { 
     Expression current = expression.Body; 

     while (current.NodeType == ExpressionType.Convert || current.NodeType == ExpressionType.TypeAs) 
     { 
      current = (current as UnaryExpression).Operand; 
     } 

     if (current.NodeType != ExpressionType.MemberAccess) 
     { 
      throw new ArgumentException(); 
     } 

     propertyOrField = current as MemberExpression; 
     current = propertyOrField.Expression; 

     instance = current; 

     while (current.NodeType != ExpressionType.Parameter) 
     { 
      if (current.NodeType == ExpressionType.Convert || current.NodeType == ExpressionType.TypeAs) 
      { 
       current = (current as UnaryExpression).Operand; 
      } 
      else if (current.NodeType == ExpressionType.MemberAccess) 
      { 
       current = (current as MemberExpression).Expression; 
      } 
      else 
      { 
       throw new ArgumentException(); 
      } 
     } 

     parameter = current as ParameterExpression; 
    } 

    #region .NET 3.5 

    // Based on https://stackoverflow.com/questions/321650/how-do-i-set-a-field-value-in-an-c-expression-tree/321686#321686 
    private static Action<T, TValue> FieldSetter<T, TValue>(FieldInfo field) 
    { 
     DynamicMethod m = new DynamicMethod("setter", typeof(void), new Type[] { typeof(T), typeof(TValue) }, typeof(FluentTools)); 
     ILGenerator cg = m.GetILGenerator(); 

     // arg0.<field> = arg1 
     cg.Emit(OpCodes.Ldarg_0); 
     cg.Emit(OpCodes.Ldarg_1); 
     cg.Emit(OpCodes.Stfld, field); 
     cg.Emit(OpCodes.Ret); 

     return (Action<T, TValue>)m.CreateDelegate(typeof(Action<T, TValue>)); 
    } 

    // Based on https://stackoverflow.com/questions/208969/assignment-in-net-3-5-expression-trees/3972359#3972359 
    private static Expression FieldSetter(Expression left, Expression right) 
    { 
     return 
      Expression.Call(
       null, 
       typeof(FluentTools) 
        .GetMethod("AssignTo", BindingFlags.NonPublic | BindingFlags.Static) 
        .MakeGenericMethod(left.Type), 
       left, 
       right); 
    } 

    private static void AssignTo<T>(ref T left, T right) // note the 'ref', which is 
    {              // important when assigning 
     left = right;          // to value types! 
    } 

    #endregion 
} 
+0

Bu çok umut verici görünüyor, CreateDelegate çağrısı zor bir argumentexception alıyorum, "Hedef yöntemine bağlanma hatası." hata ayıklama modundaki ifadenin bir NodeType of Parameter'ı vardır ve ayarlamaya çalıştığım üye bir DateTime'dır –

+0

@Mvision Neyi bağlamaya çalışıyorsunuz? Bu sadece genel özellikler üzerinde çalışacaktır. – xanatos

+0

Bağlantılı olduğum tüm mülklerin genel olduğunu varsayıyorum, EF4 varlıklarından bunları şu şekilde dağıtıyorum: customer => customer.DateCreated etc –

1

Bir özellik için özellik bilgisi döndüren bu yardımcı yöntem vardır:

public static PropertyInfo GetPropertyInfo<T, U>(Expression<Func<T, U>> property) where T : class 
{ 
    var memberExpression = (property.Body as MemberExpression); 

    if (memberExpression != null && memberExpression.Member is PropertyInfo) 
    { 
     return memberExpression.Member as PropertyInfo; 
    } 

    throw new InvalidOperationException("Invalid usage of GetPropertyInfo"); 
} 

Kullanımı: GetPropertyInfo((MyClass c) => c.PropertyName);

Daha sonra bir sınıf üzerinde özelliğin değerini ayarlamak için PropertyInfo kullanabilirsiniz.

Kodu gereksinimlerinize uyacak şekilde değiştirmeniz gerekecektir, ancak umarım yardımcı olacaktır.

2
static Expression<Action<T, TProperty>> MakeSetter<T, TProperty>(Expression<Func<T, TProperty>> getter) 
{ 
    var memberExpr = (MemberExpression)getter.Body; 
    var @this = Expression.Parameter(typeof(T), "$this"); 
    var value = Expression.Parameter(typeof(TProperty), "value"); 
    return Expression.Lambda<Action<T, TProperty>>(
     Expression.Assign(Expression.MakeMemberAccess(@this, memberExpr.Member), value), 
     @this, value); 
} 
0

bu doğru cevap bana (koleksiyonları ifadesinde) için çalışmaya ancak doğru yönde beni itti vermedi gibi yolumu

public static Action<T, object> GenerateSetterAction<T>(PropertyInfo pi) 
    { 
     //p=> p.<pi>=(pi.PropertyType)v 

     var expParamP = Expression.Parameter(typeof(T), "p"); 
     var expParamV = Expression.Parameter(typeof(object), "v"); 

     var expParamVc = Expression.Convert(expParamV, pi.PropertyType); 

     var mma = Expression.Call(
       expParamP 
       , pi.GetSetMethod() 
       , expParamVc 
      ); 

     var exp = Expression.Lambda<Action<T, object>>(mma, expParamP, expParamV); 

     return exp.Compile(); 
    } 
0

, ben bu ncelemeye Çok fazla ve ben gerçekten herhangi bir üye ifade için setter oluşturabilir bir yöntem ile geldi düşünüyorum.

Özellikler ve alanlar için işaretli yanıtla aynı şekilde davranır (bunun çok daha saydam olmasına rağmen).

Listeler ve Sözlükler için ek desteğe sahiptir - lütfen yorumlara bakın.

public static Action<TObject, TPropertyOnObject> GetSetter<TObject, TPropertyOnObject>(Expression<Func<TObject, TPropertyOnObject>> getterExpression) 
    { 
     /*** SIMPLE PROPERTIES AND FIELDS ***/ 
     // check if the getter expression reffers directly to a PROPERTY or FIELD 
     var memberAcessExpression = getterExpression.Body as MemberExpression; 
     if (memberAcessExpression != null) 
     { 
      //to here we assign the SetValue method of a property or field 
      Action<object, object> propertyOrFieldSetValue = null; 

      // property 
      var propertyInfo = memberAcessExpression.Member as PropertyInfo; 
      if (propertyInfo != null) 
      { 
       propertyOrFieldSetValue = (declaringObjectInstance, propertyOrFieldValue) => propertyInfo.SetValue(declaringObjectInstance, propertyOrFieldValue); 
      }; 

      // field 
      var fieldInfo = memberAcessExpression.Member as FieldInfo; 
      if (fieldInfo != null) 
      { 
       propertyOrFieldSetValue = (declaringObjectInstance, propertyOrFieldValue) => fieldInfo.SetValue(declaringObjectInstance, propertyOrFieldValue); 
      } 

      // This is the expression to get declaring object instance. 
      // Example: for expression "o=>o.Property1.Property2.CollectionProperty[3].TargetProperty" it gives us the "o.Property1.Property2.CollectionProperty[3]" part 
      var memberAcessExpressionCompiledLambda = Expression.Lambda(memberAcessExpression.Expression, getterExpression.Parameters.Single()).Compile(); 
      Action<TObject, TPropertyOnObject> setter = (expressionParameter, value) => 
      { 
       // get the object instance on which is the property we want to set 
       var declaringObjectInstance = memberAcessExpressionCompiledLambda.DynamicInvoke(expressionParameter); 
       Debug.Assert(propertyOrFieldSetValue != null, "propertyOrFieldSetValue != null"); 
       // set the value of the property 
       propertyOrFieldSetValue(declaringObjectInstance, value); 
      }; 

      return setter; 
     } 


     /*** COLLECTIONS (IDictionary<,> and IList<,>) ***/ 
     /* 
     * DICTIONARY: 
     * Sample expression: 
     *  "myObj => myObj.Property1.ListProperty[5].AdditionalInfo["KEY"]" 
     * Setter behaviour: 
     *  The same as adding to a dictionary. 
     *  It does Add("KEY", <value to be set>) to the dictionary. It fails if the jey already exists. 
     *  
     * 
     * LIST 
     * Sample expression: 
     *  "myObj => myObj.Property1.ListProperty[INDEX]" 
     * Setter behaviour: 
     *  If INDEX >= 0 and the index exists in the collection it behaves the same like inserting to a collection. 
     *  IF INDEX < 0 (is negative) it adds the value at the end of the collection. 
     */ 
     var methodCallExpression = getterExpression.Body as MethodCallExpression; 
     if (
      methodCallExpression != null && methodCallExpression.Object != null && 
      methodCallExpression.Object.Type.IsGenericType) 
     { 
      var collectionGetterExpression = methodCallExpression.Object as MemberExpression; 
      Debug.Assert(collectionGetterExpression != null, "collectionGetterExpression != null"); 

      // This gives us the collection instance when it is invoked on the object instance whic the expression is for 
      var collectionGetterCompiledLambda =Expression.Lambda(collectionGetterExpression, getterExpression.Parameters.Single()).Compile(); 

      // this returns the "KEY" which is the key (object) in case of Dictionaries and Index (integer) in case of other collections 
      var collectionKey = ((ConstantExpression) methodCallExpression.Arguments[0]).Value; 
      var collectionType = collectionGetterExpression.Type; 

      // IDICTIONARY 
      if (collectionType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) 
      { 
       // Create an action which accepts the instance of the object which the "dictionarry getter" expression is for and a value 
       // to be added to the dictionary. 
       Action<TObject, TPropertyOnObject> dictionaryAdder = (expressionParameter, value) => 
       { 
        try 
        { 
         var dictionaryInstance = (IDictionary)collectionGetterCompiledLambda.DynamicInvoke(expressionParameter); 
         dictionaryInstance.Add(collectionKey, value); 
        } 
        catch (Exception exception) 
        { 
         throw new Exception(
          string.Format(
           "Addition to dictionary failed [Key='{0}', Value='{1}']. The \"adder\" was generated from getter expression: '{2}'.", 
           collectionKey, 
           value, 
           getterExpression.ToString()), exception); 
        } 
       }; 

       return dictionaryAdder; 
      } 

      // ILIST 
      if (typeof (IList<>).MakeGenericType(typeof (bool)).IsAssignableFrom(collectionType.GetGenericTypeDefinition().MakeGenericType(typeof(bool)))) 
      { 
       // Create an action which accepts the instance of the object which the "collection getter" expression is for and a value 
       // to be inserted 
       Action<TObject, TPropertyOnObject> collectionInserter = (expressionParameter, value) => 
       { 
        try 
        { 
         var collectionInstance = (IList<TPropertyOnObject>)collectionGetterCompiledLambda.DynamicInvoke(expressionParameter); 
         var collectionIndexFromExpression = int.Parse(collectionKey.ToString()); 

         // The semantics of a collection setter is to add value if the index in expression is <0 and set the item at the index 
         // if the index >=0. 
         if (collectionIndexFromExpression < 0) 
         { 
          collectionInstance.Add(value); 
         } 
         else 
         { 
          collectionInstance[collectionIndexFromExpression] = value; 
         } 
        } 
        catch (Exception invocationException) 
        { 
         throw new Exception(
          string.Format(
           "Insertion to collection failed [Index='{0}', Value='{1}']. The \"inserter\" was generated from getter expression: '{2}'.", 
           collectionKey, 
           value, 
           getterExpression.ToString()), invocationException); 
        } 
       }; 

       return collectionInserter; 
      } 

      throw new NotSupportedException(
       string.Format(
        "Cannot generate setter from the given expression: '{0}'. Collection type: '{1}' not supported.", 
        getterExpression, collectionType)); 
     } 

     throw new NotSupportedException("Cannot generate setter from the given expression: "+getterExpression); 
    } 
İlgili konular