65.9K
CodeProject 正在变化。 阅读更多。
Home

通用 DynamicObject 代理和快速反射代理

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2010 年 9 月 15 日

CPOL

5分钟阅读

viewsIcon

43618

downloadIcon

668

通过包装实体使用 DynamicObject 来扩展功能。使用缓存和表达式提高反射性能

引言

不久前,我发现了一篇非常有趣的文章 "使用 DynamicObject 实现通用代理类"。这并不是第一个基于 DynamicObject 的代理解决方案,我开始思考如何统一所有解决方案并提高性能。本文是我调查的结果。

我的解决方案解决了一些问题,并为原始解决方案添加了一些功能

  • 比反射更快的属性访问速度
  • 以与属性相同的方式访问字段,并可选择访问非公共成员
  • 可以使用自定义实现的代理而不是基于反射的代理

我提取了基本的动态代理逻辑,并将单个动态代理分离成一系列动态和静态代理。动态代理继承 DynamicObject 并包装 IStaticProxy 接口,以允许使用动态语法进行访问。静态代理定义了对底层对象及其属性和方法的访问。您可以实现自定义代理并将其传递给动态代理。静态代理的默认实现是 ReflectionProxy

性能

通过反射访问速度非常慢。为了提高性能,我开发了两个想法:属性缓存和基于表达式的属性访问。与 Type.GetProperty()Type.GetProperties().First(...) 相比,我使用更快的 Dictionary 作为缓存。与使用 PropertyInfo.GetValue()SetValue() 相比,我编译 getter 或 setter lambda 表达式并调用它。由于编译时间,单次使用速度较慢,但多次使用速度快得多。

DynamicObject 简述

DynamicObject 类使您能够定义可以在动态对象上执行哪些操作以及如何执行这些操作。例如,您可以定义尝试 getset 对象属性或调用方法时会发生什么。

如果您想为模块创建更方便的接口,此类会很有用。例如,而不是像 obj.SetProperty("Count", 1) 这样难看的语法,您可以提供使用更简单的语法 obj.Count = 1 的能力。属性名将在运行时解析。要实现动态行为,可以继承 DynamicObject 类并重写必要的方法。例如,如果需要设置和获取属性的操作,可以重写 TrySetMemberTryGetMember 方法。

我们将使用它来包装静态代理。通过静态代理访问任何属性。根据静态代理的实现,您可以访问对象属性,或 e.g. 数据库中的字段,或 INI 文件中的条目等。

库的类图

Click to enlarge image

灰色显示的类是来自原始文章的代码,已适应新的基本功能。我之所以在这里放置这些代码,只是为了兼容性问题,但原始代码和更改可以在 原始文章 中找到。我对这些代码的名称和结构进行了一些更改。DynamicProxy 被拆分为 NotifyingProxy 和新的 DynamicProxy,前者接收 INotifyPropertyChanged 接口的精简功能,后者接收包装静态代理和底层对象的所有基本功能。而不是使用两个 ValidationProxy 类,我将主要的验证功能提取到 IPropertyValidator 接口中,并创建了两个相应的实现。此方案允许派生类 DataErrorInfoProxy 与其中任何一个或任何自定义实现一起使用。

DynamicProxy

这是进一步实现包装器的基类。它通过 IStaticProxy 接口提供了一个简单的实现来访问底层对象的属性

  public class DynamicProxy : DynamicObject
  {
    public DynamicProxy(IStaticProxy proxy)
    {
      if (proxy == null)
        throw new ArgumentNullException("proxy");
      Proxy = proxy;
    }

    public DynamicProxy(object proxiedObject)
    {
      if (proxiedObject == null)
        throw new ArgumentNullException("proxiedObject");
      Proxy = proxiedObject is IStaticProxy ? 
	(IStaticProxy)proxiedObject : new ReflectionProxy(proxiedObject);
    }

    protected IStaticProxy Proxy { get; private set; }

    protected virtual void SetMember(string propertyName, object value) 
      { Proxy.SetProperty(propertyName, value); }
    protected virtual object GetMember(string propertyName) 
      { return Proxy.GetProperty(propertyName); }

    public override bool TryConvert(ConvertBinder binder, out object result)
    {
      Debug.Assert(Proxy != null);
      if (binder.Type == typeof(IStaticProxy))
      {
        result = Proxy;
        return true;
      }
      if (Proxy.ProxiedObject != null && 
	binder.Type.IsAssignableFrom(Proxy.ProxiedObject.GetType()))
      {
        result = Proxy.ProxiedObject;
        return true;
      }
      return base.TryConvert(binder, out result);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
      Debug.Assert(Proxy != null);
      try
      {
        result = GetMember(binder.Name);
        return true;
      }
      catch(Exception ex)
      {
        throw new InvalidOperationException("Cannot get member", ex);
      }
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
      Debug.Assert(Proxy != null);
      try
      {
        SetMember(binder.Name, value);
        return true;
      }
      catch(Exception ex)
      {
        throw new InvalidOperationException("Cannot set member", ex);
      }
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, 
	object[] args, out object result)
    {
      Debug.Assert(Proxy != null);
      return Proxy.TryInvokeMethod(binder.Name, out result, args) || 
	base.TryInvokeMember(binder, args, out result);
    }
  }

关注点

实现了 TryConvert 方法,以便在用户隐式尝试将 DynamicProxy 对象转换为另一种类型时允许执行某些特殊操作。如果对象被转换为底层对象的类型,则返回底层对象。转换为 IStaticProxy 接口时,返回静态代理对象。

IStaticProxy

这是进一步实现对不同类型实体访问的接口。

  public interface IStaticProxy
  {
    object ProxiedObject { get; }
    Type GetPropertyType(string propertyName);
    void SetProperty(string propertyName, object value);
    object GetProperty(string propertyName);
    bool TrySetProperty(string propertyName, object value);
    bool TryGetProperty(string propertyName, out object result);
    object InvokeMethod(string methodName, params object[] args);
    bool TryInvokeMethod(string methodName, out object result, params object[] args);
  }

ReflectionProxy

它实现了 IStaticProxy,并允许使用 .NET 的反射引擎访问底层对象的成员和方法。

  public class ReflectionProxy : IStaticProxy
  {
    protected readonly TypeInfoCache TypeInfoCache;
    protected readonly bool FullAccess;

    public ReflectionProxy(object proxiedObject, bool fullAccess = false, 
	Type forceType = null)
    {
      if (proxiedObject == null) throw new ArgumentNullException("proxiedObject");
      ProxiedObject = proxiedObject;
      if (forceType != null)
      {
        if (!ProxiedObject.GetType().IsSubclassOf(forceType) && 
		ProxiedObject.GetType() != forceType)
          throw new ArgumentException("Forced type should be super 
		class of the object type");
      }
      else
        forceType = ProxiedObject.GetType();
      TypeInfoCache = GlobalTypeInfoCache.GetTypeInfo(forceType);
      FullAccess = fullAccess;
    }
    [...]
    #region IStaticProxy implementation
    public object ProxiedObject { get; private set; }
    public Type GetPropertyType(string propertyName)
    {
      var pie = TypeInfoCache.GetPropertyInfoEx(propertyName, FullAccess);
      if (pie == null)
        throw new ArgumentException("Property " + propertyName + 
	" doesn't exist in type " + TypeInfoCache.Type);
      return pie.Type;
    }
    public virtual void SetProperty(string propertyName, object value)
    {
      var pie = TypeInfoCache.GetPropertyInfoEx(propertyName, FullAccess);
      if (pie == null)
        throw new ArgumentException("Property " + propertyName + 
		" doesn't exist in type " + TypeInfoCache.Type);
      var setter = pie.FastSetter;
      if (setter == null)
      {
        if (FullAccess) setter = pie.Setter;
        if (setter == null)
          throw new ArgumentException("Property " + propertyName + 
		" doesn't have write access in type " + TypeInfoCache.Type);
      }
      if (pie.Type != value.GetType())
        value = Convert.ChangeType(value, pie.Type);
      setter(ProxiedObject, value);
    }
    public virtual object GetProperty(string propertyName)
    {
      var pie = TypeInfoCache.GetPropertyInfoEx(propertyName, FullAccess);
      if (pie == null)
        throw new ArgumentException("Property " + propertyName + 
		" doesn't exist in type " + TypeInfoCache.Type);
      var getter = pie.FastGetter;
      if (getter == null)
      {
        if (FullAccess) getter = pie.Getter;
        if (getter == null)
          throw new ArgumentException("Property " + propertyName + 
		" doesn't have read access in type " + TypeInfoCache.Type);
      }
      return getter(ProxiedObject);
    }
    public virtual bool TrySetProperty(string propertyName, object value)
    {
      [...]
    }
    public virtual bool TryGetProperty(string propertyName, out object result)
    {
      [...]
    }
    public virtual object InvokeMethod(string methodName, params object[] args)
    {
      [...]
    }
    public virtual bool TryInvokeMethod(string methodName, 
	out object result, params object[] args)
    {
      [...]
    }
    #endregion
  }

关注点

它将字段视为属性。如果找不到指定名称的属性,它会搜索同名字段。
参数 FullAccess 允许访问非公共成员。它使用全局类型信息缓存来提高反射性能。

TypeInfoCache

此类在 Dictionary 中缓存 PropertyInfoFieldInfo 反射信息。

  public class TypeInfoCache
  {
    [...]
    private readonly Dictionary<string, PropertyInfoEx> _propertyInfoMap = 
		new Dictionary<string, PropertyInfoEx>();
    public readonly Type Type;
    public TypeInfoCache(Type type) { Type = type; }
    private const BindingFlags DefaultLookup = 
	BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
    public PropertyInfoEx GetPropertyInfoEx
	(string propertyName, bool fullAccess = false)
    {
      PropertyInfoEx pie;
      if (!_propertyInfoMap.TryGetValue(propertyName, out pie))
      {
        PropertyInfo pi = GetPropertyInfo
		(propertyName, DefaultLookup | BindingFlags.NonPublic);
        if (pi != null)
          _propertyInfoMap.Add(propertyName, pie = new PropertyInfoEx(pi));
        else
        {
          // if property not found - search for field with the same name
          FieldInfo fi = GetFieldInfo
		(propertyName, DefaultLookup | BindingFlags.NonPublic);
          if (fi != null)
            _propertyInfoMap.Add(propertyName, pie = new PropertyInfoEx(fi));
        }
      }
      if (pie != null && (fullAccess || pie.IsPublic)) return pie;
      return null;
    }
    private PropertyInfo GetPropertyInfo(string propertyName, BindingFlags lookup)
    {
      try
      {
        return Type.GetProperty(propertyName, lookup);
      }
      catch (Exception)
      {
        foreach (var propertyInfo in Type.GetProperties(lookup))
          if (propertyInfo.Name == propertyName)
            return propertyInfo;
      }
      return null;
    }
    private FieldInfo GetFieldInfo(string fieldName, BindingFlags lookup)
    {
      [...]
    }
    [...]
  }

PropertyInfoEx

这个内部类统一了对属性和字段的访问,并保存了成员的 setter 和 getter。

  public class PropertyInfoEx
  {
    internal PropertyInfoEx(PropertyInfo propertyInfo)
    {
      Debug.Assert(propertyInfo != null, "PropertyInfo should be specified");
      PropertyInfo = propertyInfo;
      IsPublic = propertyInfo.GetGetMethod() != null || 
			propertyInfo.GetSetMethod() != null;
    }
    internal PropertyInfoEx(FieldInfo fieldInfo)
    {
      Debug.Assert(fieldInfo != null, "FieldInfo should be specified");
      FieldInfo = fieldInfo;
      IsPublic = !fieldInfo.IsPrivate && fieldInfo.IsPublic;
    }
    public PropertyInfo PropertyInfo { get; private set; }
    public FieldInfo FieldInfo { get; private set; }
    #region FastGetter
    private bool _getterInaccessible;
    private Func<object, object> _getter;
    public Func<object, object> FastGetter
    {
      get
      {
        if (_getter == null && !_getterInaccessible)
        {
          _getter = PropertyInfo != null ? 
	    PropertyInfo.GetValueGetter<object>() : FieldInfo.GetValueGetter<object>();
          if (_getter == null) _getterInaccessible = true;
        }
        return _getter;
      }
    }
    #endregion
    #region FastSetter
    private bool _setterInaccessible;
    private Action<object, object> _setter;
    public Action<object, object> FastSetter
    {
      get
      {
        if (_setter == null && !_setterInaccessible)
        {
          _setter = PropertyInfo != null ? 
	    PropertyInfo.GetValueSetter<object>() : FieldInfo.GetValueSetter<object>();
          if (_setter == null) _setterInaccessible = true;
        }
        return _setter;
      }
    }
    #endregion
    #region Getter
    public Func<object, object> Getter
    {
      get
      {
        if (PropertyInfo == null || PropertyInfo.CanRead) return TheGetter;
        return null;
      }
    }
    private object TheGetter(object theObject) 
      { return PropertyInfo != null 
          ? PropertyInfo.GetValue(theObject, null) 
          : FieldInfo.GetValue(theObject); }
    #endregion
    #region Setter
    public Action<object, object> Setter
    {
      get
      {
        if (PropertyInfo != null)
        {
          if (PropertyInfo.CanWrite)
            return TheSetter;
        }
        else if (!FieldInfo.IsInitOnly)
          return TheSetter;
        return null;
      }
    }
    public void TheSetter(object theObject, object value)
    {
      if (PropertyInfo != null)
        PropertyInfo.SetValue(theObject, value, null);
      else // if(FieldInfo != null)
        FieldInfo.SetValue(theObject, value);
    }
    #endregion
    public bool IsPublic { get; private set; }
    public Type Type { get { return PropertyInfo != null ? 
	PropertyInfo.PropertyType : FieldInfo.FieldType; } }
  } 

关注点

FastSetterFastGetter 通过使用动态编译的 lambda 表达式而不是通过反射访问,实现了对公共成员的快速访问。

FieldInfoExtensions 和 PropertyInfoExtensions

这些类各实现两个扩展函数。

  • Func<object, T> GetValueGetter<T>()
  • Action<object, T> GetValueSetter<T>()

这些函数动态地构建 lambda 表达式来 getset 指定的字段或属性。

  public static class PropertyInfoExtensions
  {
    public static Func<object, T> GetValueGetter<T>(this PropertyInfo propertyInfo)
    {
      if (!propertyInfo.CanRead || propertyInfo.GetGetMethod() == null) return null;
      var instance = Expression.Parameter(typeof(Object), "i");
      var castedInstance = Expression.ConvertChecked
		(instance, propertyInfo.DeclaringType);
      var property = Expression.Property(castedInstance, propertyInfo);
      var convert = Expression.Convert(property, typeof(T));
      var expression = Expression.Lambda(convert, instance);
      return (Func<object, T>)expression.Compile();
    }
    public static Action<object, T> GetValueSetter<T>(this PropertyInfo propertyInfo)
    {
      if (!propertyInfo.CanWrite || propertyInfo.GetSetMethod() == null) return null;
      var instance = Expression.Parameter(typeof(Object), "i");
      var castedInstance = Expression.ConvertChecked
		(instance, propertyInfo.DeclaringType);
      var argument = Expression.Parameter(typeof(T), "a");
      var setterCall = Expression.Call(
        castedInstance,
        propertyInfo.GetSetMethod(),
        Expression.Convert(argument, propertyInfo.PropertyType));
      return Expression.Lambda<Action<object, T>>
		(setterCall, instance, argument).Compile();
    }
  }
  public static class FieldInfoExtensions
  {
    public static Func<object, T> GetValueGetter<T>(this FieldInfo fieldInfo)
    {
      if (!fieldInfo.IsPublic) return null;
      var instance = Expression.Parameter(typeof(Object), "i");
      var castedInstance = Expression.ConvertChecked
			(instance, fieldInfo.DeclaringType);
      var field = Expression.Field(castedInstance, fieldInfo);
      var convert = Expression.Convert(field, typeof(T));
      var expression = Expression.Lambda(convert, instance);
      return (Func<object, T>)expression.Compile();
    }
    public static Action<object, T> GetValueSetter<T>(this FieldInfo fieldInfo)
    {
      if (!fieldInfo.IsPublic || fieldInfo.IsInitOnly) return null;
      var instance = Expression.Parameter(typeof(Object), "i");
      var castedInstance = Expression.ConvertChecked
			(instance, fieldInfo.DeclaringType);
      var argument = Expression.Parameter(typeof(T), "a");
      var setter = Expression.Assign(
        Expression.Field(castedInstance, fieldInfo),
        Expression.Convert(argument, fieldInfo.FieldType));
      return Expression.Lambda<Action<object, T>>
		(setter, instance, argument).Compile();
    }
  }

FullAccessProxy

此类允许访问底层对象的非公共成员。

  internal class FullAccessProxy : DynamicProxy
  {
    public FullAccessProxy(object o, Type forceType = null) :
      base(new ReflectionProxy(o, true, forceType)) { }
  }

关注点

此类最初非常复杂,几乎完全复制了 DynamicProxyReflectionProxy 类的功能,除了提高速度的功能。此外,它与原始文章中的 DynamicProxy 非常相似。现在,它只包含一个构造函数,该构造函数将代理限制为具有 FullAccess 权限的 ReflectionProxy

性能测量

原始代码使用构造 object.GetType().GetProperties().First(...) 来获取 PropertyInfo。使用 Dictionary 比使用 object.GetType().GetProperty(...)100 倍,比其快约 5-7 倍
使用编译的 lambda 表达式比反射快约 10 倍。但是,编译表达式要慢约 300 倍。因此,我们在第一次访问每个属性时只编译一次表达式。
无论如何,我们无法接近直接属性访问的性能。在我的测试中,静态代理访问比直接访问慢约 100 倍,动态代理访问慢约 150-300 倍
另一方面,如果我们测量实现了 INotifyPropertyChanged 的属性设置,它看起来好得多。在这种情况下,动态访问仅慢 2-4 倍

动态对象

DynamicObject 类有一些很好的特性。其中之一是能够在派生类中声明真实属性,并以与虚拟属性相同的方式访问它们。反射首先检查真实属性,只有在找不到匹配的属性时,它才会调用 TryGetMember。但是,这可能会导致意外问题。代理中的属性可以隐藏底层对象中的属性,如果名称相同,则使其完全不可用。因此,我试图在构造函数中初始化大多数参数。

待办事项

  • 实现方法的反射信息缓存
  • 通过表达式调用方法

历史

  • 1.0 - 初始版本
© . All rights reserved.