开发者

How to dynamically set a property of a class without using reflection (with dynamic) in C# 4 when property name is coming from another source

I'm building/updating an EntityFramework EntityObject on runtime. I want to set the properties of the entity class, property names and values are coming from another source.

So I'm doing this;

    public static Ent开发者_运维问答ityCollection<T> UpdateLocaleEntity<T>(EntityCollection<T> entityCollectionToUpdate, params ILocaleControl[] values) where T : EntityObject
    {
        foreach (var x in entityCollectionToUpdate)
        {
            Type t = typeof(T);
            dynamic localeEntity = x;

            string cultureCode = localeEntity.CultureCode;

            for (int j = 0; j < values.Length; j++)
            {
                var value = values[j].GetLocaleValue(cultureCode);
                t.GetProperty(values[j].EntityPropertyName).SetValue(localeEntity, value, null);
            }
        }

        return entityCollectionToUpdate;
    }

So, how can I get rid of "t.GetProperty(values[j].EntityPropertyName).SetValue(localeEntity, value, null);" part, is there a dynamic way of doing this?

Something like;

dynamicCastedLocaleEntity.GetProperty(values[j].EntityPropertyName) = value;

Thanks.


Long answer coming up. Reflection is great in many situations, horrible in some but in almost all cases it's slow.

There are at least 4 different ways to set a property in .NET without having to use reflection.

I thought I demonstrate one of them: Using compiled expression trees. Note that the expression building is rather expensive too so that's why it's very important to cache the delegate one builds with it in a dictionary (for instance):

Expression Trees was introduced in .NET35 and is used for many things. Here I use them to build a property setter expression and then compile it into a delegate.

The example demonstrates different timing for the different cases but here are my numbers: Control case (hard coded): 0.02s Reflection: 1.78s Expression Tree: 0.06s

using System;
using System.Linq.Expressions;

namespace DifferentPropertSetterStrategies
{
   class TestClass
   {
      public string XY
      {
         get;
         set;
      }
   }

   class DelegateFactory
   {
      public static Action<object, object> GenerateSetPropertyActionForControl(
         )
      {
         return (inst, val) => ((TestClass) inst).XY = (string) val;
      }

      public static Action<object, object> GenerateSetPropertyActionWithReflection(
         Type type,
         string property
         )
      {
         var propertyInfo = type.GetProperty(property);

         return (inst, val) => propertyInfo.SetValue (inst, val, null);
      }

      public static Action<object,object> GenerateSetPropertyActionWithLinqExpression (
         Type type,
         string property
         )
      {
         var propertyInfo = type.GetProperty(property);
         var propertyType = propertyInfo.PropertyType;

         var instanceParameter = Expression.Parameter(typeof(object), "instance");
         var valueParameter = Expression.Parameter(typeof(object), "value");

         var lambda = Expression.Lambda<Action<object, object>> (
            Expression.Assign (
               Expression.Property (Expression.Convert (instanceParameter, type), propertyInfo),
               Expression.Convert(valueParameter, propertyType)),
            instanceParameter,
            valueParameter
            );

         return lambda.Compile();
      }
   }

   static class Program
   {
      static void Time (
         string tag, 
         object instance,
         object value,
         Action<object, object > action
         )
      {
         // Cold run
         action(instance, value);

         var then = DateTime.Now;
         const int Count = 2000000;
         for (var iter = 0; iter < Count; ++iter)
         {
            action (instance, value);
         }
         var diff = DateTime.Now - then;
         Console.WriteLine ("{0} {1} times - {2:0.00}s", tag, Count, diff.TotalSeconds);

      }

      static void Main(string[] args)
      {
         var instance = new TestClass ();
         var instanceType = instance.GetType ();

         const string TestProperty = "XY";
         const string TestValue = "Test";

         // Control case which just uses a hard coded delegate
         Time(
            "Control",
            instance,
            TestValue,
            DelegateFactory.GenerateSetPropertyActionForControl ()
            );

         Time(
            "Reflection", 
            instance, 
            TestValue, 
            DelegateFactory.GenerateSetPropertyActionWithReflection (instanceType, TestProperty)
            );

         Time(
            "Expression Trees", 
            instance, 
            TestValue, 
            DelegateFactory.GenerateSetPropertyActionWithLinqExpression(instanceType, TestProperty)
            );

         Console.ReadKey();
      }

   }
}


For FuleSnabel's answer, you can speed it up a lot (sometimes twice as fast in my tests). In some tests, it was just as fast as the Control solution:

public static Action<Object,Object> GenerateSetPropertyActionWithLinqExpression2(Type type, String property) {
    PropertyInfo pi = type.GetProperty(property,BindingFlags.Instance|BindingFlags.Public);
    MethodInfo mi = pi.GetSetMethod();
    Type propertyType = pi.PropertyType;

    var instance = Expression.Parameter(typeof(Object), "instance");
    var value = Expression.Parameter(typeof(Object), "value");

    var instance2 = Expression.Convert(instance, type);
    var value2 = Expression.Convert(value, pi.PropertyType);
    var callExpr = Expression.Call(instance2, mi, value2);

    return Expression.Lambda<Action<Object,Object>>(callExpr, instance, value).Compile();
}


possibly not with EntityObject, but if you've had an ExpandoObject than you can do

dynamic entity = new ExpandoObject();
(entity as IDictionary<String, Object>)[values[j].EntityPropertyName] = value


The open source framework ImpromptuInterface has methods to invoke based on a string using the DLR rather than reflection and runs faster than reflection too.

Impromptu.InvokeSet(localeEntity, values[j].EntityPropertyName,value);


I'm afraid not. Any use of a dynamic object is baked-in at compile time. Any call which could vary at run-time has to be done using reflection.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜