• C#如何使用反射技术访问类的私有成员?
  • 发布于 2个月前
  • 214 热度
    0 评论
  • pckillers
  • 0 粉丝 36 篇博客
  •   
基于封装的原则,API 的设计者会将部分成员(属性、字段、方法等)隐藏以保证健壮性。但总有需要直接访问这些私有成员的情况。为了访问一个类型的私有成员,除了更改 API 设计还有就是使用反射技术:
public class MyApi
{
    public MyApi()
    {
        _createdAt = DateTime.Now;
    }
    private DateTime _createdAt;
    public int ShowTimes
    {
        get;
        private set;
    }
    public void ShowCreateTime()
    {
        Console.WriteLine(_createdAt);
        ShowTimes++;
    }
}
void Main()
{
    var api = new MyApi();
    var field = api.GetType().GetField("_createdAt", BindingFlags.NonPublic | BindingFlags.Instance);
    var value = field.GetValue(api);
    Console.WriteLine(value);
}
这种写法并不优雅:
1.代码冗长,编写麻烦。

2.实现比较绕,不太直观。


笔者基于“动态类型技术”探索出了一种相对来说比较优雅的方案用于美化上述代码,并为其命名为 ReflectionDynamicObject :
void Main()
{
  var api = new MyApi();
  dynamic wrapper = ReflectionDynamicObject.Wrap(api);
  Console.WriteLine(wrapper._createdAt);
}
除了支持获取值,ReflectionDynamicObject 还支持赋值:
void Main()
{
  var api = new MyApi();
  dynamic wrapper = ReflectionDynamicObject.Wrap(api);
  wrapper._createdAt = new DateTime(2022, 2, 2, 22, 22, 22);
  api.ShowCreateTime();
}
除了字段,当然也支持对属性的操作:
void Main()
{
  var api = new MyApi();
  dynamic wrapper = ReflectionDynamicObject.Wrap(api);
  wrapper.ShowTimes = 100;
  Console.WriteLine(wraper.ShowTimes);
}
在对属性的支持上,ReflectionDynamicObject 使用了“快速反射”技术,将取值和复制操作生成了委托以优化性能。

ReflectionDynamicObject 的实现原理
ReflectionDynamicObject 派生自 DynamicObject ,其内部通过反射技术获取到所有的属性和字段并对其 getter 和 setter 方法进行存储并通过 TryGetMember 和 TrySetMember 方法经运行时调用。

ReflectionDynamicObject 的源代码:
public sealed class ReflectionDynamicObject : DynamicObject
{
        // 堆代码 duidaima.com
	private readonly object _instance;
	private readonly Accessor _accessor;
	private ReflectionDynamicObject(object instance)
	{
		_instance = instance ?? throw new ArgumentNullException(nameof(instance));
		_accessor = GetAccessor(instance.GetType());
	}
	public static ReflectionDynamicObject Wrap(Object value)
	{
		if (value == null) throw new ArgumentNullException(nameof(value));
		return new ReflectionDynamicObject(value);
	}

	public override bool TryGetMember(GetMemberBinder binder, out object result)
	{
		if (_accessor.TryFindGetter(binder.Name, out var getter))
		{
			result = getter.Get(_instance);
			return true;
		}
		return base.TryGetMember(binder, out result);
	}

	public override bool TrySetMember(SetMemberBinder binder, object value)
	{
		if (_accessor.TryFindSetter(binder.Name, out var setter))
		{
			setter.Set(_instance, value);
			return true;
		}
		return base.TrySetMember(binder, value);
	}

	#region 快速反射
	private interface IGetter
	{
		object Get(object instance);
	}
	private interface ISetter
	{
		void Set(object instance, object value);
	}

	private class Getter : IGetter
	{
		private FieldInfo _field;
		public Getter(FieldInfo field)
		{
			_field = field ?? throw new ArgumentNullException(nameof(field));
		}
		public object Get(object instance)
		{
			return _field.GetValue(instance);
		}
	}

	private class Setter : ISetter
	{
		private FieldInfo _field;
		public Setter(FieldInfo field)
		{
			_field = field ?? throw new ArgumentNullException(nameof(field));
		}
		public void Set(object instance, object value)
		{
			_field.SetValue(instance, value);
		}
	}

	private class Getter<T1, T2> : IGetter
	{
		private readonly Func<T1, T2> _getter;
		public Getter(Func<T1, T2> getter)
		{
			_getter = getter ?? throw new ArgumentNullException(nameof(getter));
		}
		public object Get(object instance)
		{
			return _getter((T1)instance);
		}
	}

	private class Setter<T1, T2> : ISetter
	{
		private readonly Action<T1, T2> _setter;
		public Setter(Action<T1, T2> setter)
		{
			this._setter = setter ?? throw new ArgumentNullException(nameof(setter));
		}
		public void Set(object instance, object value)
		{
			this._setter.Invoke((T1)instance, (T2)value);
		}
	}

	private class Accessor
	{
		public Accessor(Type type)
		{
			this._type = type ?? throw new ArgumentNullException(nameof(_type));
			var getter = new SortedDictionary<string, IGetter>();
			var setter = new SortedDictionary<string, ISetter>();

			var fields = _type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

			foreach (var field in fields)
			{
				getter[field.Name] = new Getter(field);
				setter[field.Name] = new Setter(field);
			}

			var props = _type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

			foreach (var item in props)
			{
				if (item.CanRead)
				{
					var method = item.GetMethod;
					var funcType = typeof(Func<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
					var func = method.CreateDelegate(funcType);
					var getterType = typeof(Getter<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
					var get = (IGetter)Activator.CreateInstance(getterType, func);
					getter[item.Name] = get;
				}
				if (item.CanWrite)
				{
					var method = item.SetMethod;
					var actType = typeof(Action<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
					var act = method.CreateDelegate(actType);
					var setterType = typeof(Setter<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
					var set = (ISetter)Activator.CreateInstance(setterType, act);
					setter[item.Name] = set;
				}
			}

			_getters = getter;
			_setters = setter;
		}
		private readonly Type _type;
		private readonly IReadOnlyDictionary<string, IGetter> _getters;
		private readonly IReadOnlyDictionary<string, ISetter> _setters;

		public bool TryFindGetter(string name, out IGetter getter) => _getters.TryGetValue(name, out getter);
		public bool TryFindSetter(string name, out ISetter setter) => _setters.TryGetValue(name, out setter);
	}
	private static Dictionary<Type, Accessor> _accessors = new Dictionary<Type, Accessor>();
	private static object _accessorsLock = new object();
	private static Accessor GetAccessor(Type type)
	{
		if (_accessors.TryGetValue(type, out var accessor)) return accessor;
		lock (_accessorsLock)
		{
			if (_accessors.TryGetValue(type, out accessor)) return accessor;
			accessor = new Accessor(type);
			var temp = new Dictionary<Type, Accessor>(_accessors);
			temp[type] = new Accessor(type);
			_accessors = temp;
			return accessor;
		}
	}
	#endregion
}

ReflectionDynamicObject 的局限性
基于复杂度的考虑,ReflectionDynamicObject 并未添加对“方法”的支持。这也就意味着对方法的调用是缺失的。虽然动态行为让程序摆脱了对字符串的依赖,但是该实现对“重构”的支持仍然不友好。

用户评论