嵌套属性值的动态提取优化

时间:2014-05-28 21:52:06

标签: c# .net reflection reflection.emit il

我有一小段代码负责通过反射从对象实例中动态提取属性值:

public static object ExtractValue(object source, string property)
{
    var props = property.Split('.');
    var type = source.GetType();
    var arg = Expression.Parameter(type, "x");
    Expression expr = arg;
    foreach (var prop in props)
    {
        var pi = type.GetProperty(prop);
        if (pi == null)
            throw new ArgumentException(string.Format("Field {0} not found.", prop));
        expr = Expression.Property(expr, pi);
        type = pi.PropertyType;
    }
    var delegateType = typeof(Func<,>).MakeGenericType(source.GetType(), type);
    var lambda = Expression.Lambda(delegateType, expr, arg);

    var compiledLambda = lambda.Compile();
    var value = compiledLambda.DynamicInvoke(source);
    return value;
}

它可以提取嵌套属性的值,例如:ExtractValue(instance, "PropA.PropB.PropC")

尽管我喜欢这种方法及其实现,但是,当PropBnull时,DynamicInvoke()只会抛出NullReferenceException(由TargetInvocationException包裹)。因为我需要知道null的确切属性是这样的,所以我修改了它的主体(标准的逐步提取链):

public static object ExtractValue(object source, string property)
{
    var props = property.Split('.');
    for (var i = 0; i < props.Length; i++)
    {
        var type = source.GetType();
        var prop = props[i];
        var pi = type.GetProperty(prop);
        if (pi == null)
            throw new ArgumentException(string.Format("Field {0} not found.", prop));
        source = pi.GetValue(source, null);
        if (source == null && i < props.Length - 1)
            throw new ArgumentNullException(pi.Name, "Extraction interrupted.");
    }
    return source;
}

现在它看起来有点糟糕(我喜欢lambdas)但行为要好得多,不仅因为它提供了更多有意义的失败信息,而且因为这个版本比第一个版本快66倍(下面的粗略测试) ):

var model = new ModelA
{
    PropB = new ModelB {PropC = new ModelC {PropD = new ModelD {PropE = new ModelE {PropF = "hey"}}}}
};
const int times = 1000000;

var start = DateTime.Now;
for (var i = 0; i < times; i++)
    ExtractValueFirst(model, "PropB.PropC.PropD.PropE.PropF");
var ticks_first = (DateTime.Now - start).Ticks;
Console.WriteLine(":: first  - {0} iters tooks {1} ticks", times, ticks_first);

start = DateTime.Now;
for (var i = 0; i < times; i++)
    ExtractValueSecond(model, "PropB.PropC.PropD.PropE.PropF");
var ticks_second= (DateTime.Now - start).Ticks;
Console.WriteLine(":: second - {0} iters tooks {1} ticks", times, ticks_second);

Console.WriteLine("ticks_first/ticks_second: {0}", (float)ticks_first / ticks_second);
Console.ReadLine();

enter image description here

如何在.NET中优化此代码以更快地执行(缓存,直接IL等)?

2 个答案:

答案 0 :(得分:3)

您可以通过缓存已编译的委托来显着提高性能:

static readonly ConcurrentDictionary<Tuple<Type,string>,Delegate> _delegateCache = new ConcurrentDictionary<Tuple<Type,string>,Delegate>();

public static object ExtractValue(object source, string expression)
{
    Type type = source.GetType();
    Delegate del =  _delegateCache.GetOrAdd(new Tuple<Type,string>(type,expression),key => _getCompiledDelegate(key.Item1,key.Item2));
    return del.DynamicInvoke(source);
}

// if you want to acces static aswell...
public static object ExtractStaticValue(Type type, string expression)
{
    Delegate del =  _delegateCache.GetOrAdd(new Tuple<Type,string>(type,expression),key => _getCompiledDelegate(key.Item1,key.Item2));
    return del.DynamicInvoke(null);
}

private static Delegate _getCompiledDelegate(Type type, string expression)
{
    var arg = Expression.Parameter(type, "x");
    Expression expr = arg;
    foreach (var prop in property.Split('.'))
    {
        var pi = type.GetProperty(prop);
        if (pi == null) throw new ArgumentException(string.Format("Field {0} not found.", prop));
        expr = Expression.Property(expr, pi);
        type = pi.PropertyType;
    }
    var delegateType = typeof(Func<,>).MakeGenericType(source.GetType(), type);
    var lambda = Expression.Lambda(delegateType, expr, arg);

    return lambda.Compile();
}

答案 1 :(得分:0)

我已经完成了一些执行时间测量,如下所示:

private static Func<object, object> _cachedFunc;
private static Delegate _cachedDel;

static void Main(string[] args)
{
    var model = new ModelA
    {
        PropB = new ModelB {PropC = new ModelC {PropD = new ModelD {PropE = new ModelE {PropF = "hey"}}}}
    };            
    const string property = "PropB.PropC.PropD.PropE.PropF";
    var watch = new Stopwatch();

    var t1 = MeasureTime(watch, () => ExtractValueDelegate(model, property), "compiled delegate dynamic invoke");
    var t2 = MeasureTime(watch, () => ExtractValueCachedDelegate(model, property), "compiled delegate dynamic invoke / cached");
    var t3 = MeasureTime(watch, () => ExtractValueFunc(model, property), "compiled func invoke");
    var t4 = MeasureTime(watch, () => ExtractValueCachedFunc(model, property), "compiled func invoke / cached");
    var t5 = MeasureTime(watch, () => ExtractValueStepByStep(model, property), "step-by-step reflection");
    var t6 = MeasureTime(watch, () => ExtractValueStandard(model), "standard access (model.prop.prop...)");

    Console.ReadLine();
}

public static long MeasureTime<T>(Stopwatch sw, Func<T> funcToMeasure, string funcName)
{
    const int times = 100000;
    sw.Reset();

    sw.Start();
    for (var i = 0; i < times; i++)
        funcToMeasure();
    sw.Stop();

    Console.WriteLine(":: {0, -45}  - {1} iters tooks {2, 10} ticks", funcName, times, sw.ElapsedTicks);
    return sw.ElapsedTicks;
}

public static object ExtractValueDelegate(object source, string property)
{        
    var ptr = GetCompiledDelegate(source.GetType(), property);
    return ptr.DynamicInvoke(source);            
}

public static object ExtractValueCachedDelegate(object source, string property)
{        
    var ptr = _cachedDel ?? (_cachedDel = GetCompiledDelegate(source.GetType(), property));
    return ptr.DynamicInvoke(source);
}

public static object ExtractValueFunc(object source, string property)
{        
    var ptr = GetCompiledFunc(source.GetType(), property);
    return ptr(source); //return ptr.Invoke(source);
}        

public static object ExtractValueCachedFunc(object source, string property)
{        
    var ptr = _cachedFunc ?? (_cachedFunc = GetCompiledFunc(source.GetType(), property));
    return ptr(source); //return ptr.Invoke(source);
}

public static object ExtractValueStepByStep(object source, string property)
{
    var props = property.Split('.');
    for (var i = 0; i < props.Length; i++)
    {
        var type = source.GetType();
        var prop = props[i];
        var pi = type.GetProperty(prop);
        if (pi == null)
            throw new ArgumentException(string.Format("Field {0} not found.", prop));
        source = pi.GetValue(source, null);
        if (source == null && i < props.Length - 1)
            throw new ArgumentNullException(pi.Name, "Extraction interrupted.");
    }
    return source;
}

public static object ExtractValueStandard(ModelA source)
{
    return source.PropB.PropC.PropD.PropE.PropF;
}

private static Func<object, object> GetCompiledFunc(Type type, string property)
{        
    var arg = Expression.Parameter(typeof(object), "x");
    Expression expr = Expression.Convert(arg, type);
    var propType = type;
    foreach (var prop in property.Split('.'))
    {
        var pi = propType.GetProperty(prop);
        if (pi == null) throw new ArgumentException(string.Format("Field {0} not found.", prop));
        expr = Expression.Property(expr, pi);
        propType = pi.PropertyType;
    }
    expr = Expression.Convert(expr, typeof(object));
    var lambda = Expression.Lambda<Func<object, object>>(expr, arg);
    return lambda.Compile();
}

private static Delegate GetCompiledDelegate(Type type, string property)
{        
    var arg = Expression.Parameter(type, "x");
    Expression expr = arg;
    var propType = type;
    foreach (var prop in property.Split('.'))
    {
        var pi = propType.GetProperty(prop);
        if (pi == null) throw new ArgumentException(string.Format("Field {0} not found.", prop));
        expr = Expression.Property(expr, pi);
        propType = pi.PropertyType;
    }
    var delegateType = typeof(Func<,>).MakeGenericType(type, propType);
    var lambda = Expression.Lambda(delegateType, expr, arg);
    return lambda.Compile();
}

enter image description here

顺便说一句:正如你所看到的,我省略了在字典中存储已编译的lambdas(如CSharpie的答案qiven),因为当你将它与编译的lambda执行时间进行比较时,字典查找非常耗时。