[C#.NET] 利用 dynamic 簡化反射程式碼? 實作 DynamicObject
http://www.dotblogs.com.tw/yc421206/archive/2012/10/31/79794.aspx
續上篇,dynamic 在處理反射公開方法時可以讓我們省掉一些程式碼,不過在處理非公開方法,討不太到便宜
預設,dynamic 只會處理公開成員,我想要它也能處理非公開方法,以便我處理單元測試。
本文章節:
實作 TransparentDynamicObject<T>:
測試 TransparentDynamicObject<T>:
開始前要有測試類別
{ private int Func1(int a) { var result = a += 1; return result; } protected int Func2(int a) { var result = a += 1; return result; } internal int Func3(int a) { var result = a += 1; return result; } private static int Func4(int a) { var result = a += 1; return result; } private int Prop { get; set; } private string Func5<T>(T a) { return a.ToString(); } } public static class Foo { private static string TransformString(string s) { return s.ToLower(); } private static int Func1(int a) { var result = a += 1; return result; } private static string Func2<T>(T a) { return a.ToString(); } }
下面的寫法,應該不莫生了
public void 一般私有方法() { var assemblyType = typeof(Service); dynamic instance = Activator.CreateInstance(assemblyType); object[] para = new object[] { 1 }; dynamic result = assemblyType.InvokeMember( "Func1", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, instance, para); Assert.AreEqual(2, result); }
調用泛型私有方法
public void 泛型私有方法() { Service creator = new Service(); int param = 5; var method = typeof(Service) .GetMethod("ExecuteGeneric", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); method = method.MakeGenericMethod(param.GetType()); var result = method.Invoke(creator, new object[] { param }); Assert.AreEqual(param.ToString(), result); }
每次都要這樣來一下實在太累人,我們可以將這個動作封裝成擴充方法,,我想要改變它的行為,我想要它也能處理非公開成員,參考下篇做法
http://bugsquash.blogspot.tw/2009/05/testing-private-methods-with-c-40.html
Link 內文所演示的程式碼,有用到 CSharpInvokeMemberBinder,但它並不是公開類別,沒有它會導致調用泛型方法失敗
解法,可以參考下篇:
實作 TransparentDynamicObject<T>:
TransparentDynamicObject<T> 實作 DynamicObject
- 覆寫 TrySetMember,設定成員
- 覆寫 TryGetMember,取得成員
- 覆寫 TryInvokeMember ,調用方法
實作 DynamicObject,最難處理的是泛型方法的調用,上述連結是利用 il 來處理泛型方法的 T,這相當的酷 (雖然我搞太不懂它在幹嘛 XD)
完整程式碼:
{ public static dynamic AsTransparentDynamic<T>(this T o) { return new TransparentDynamicObject<T>(o); } } public class TransparentDynamicObject<T> : DynamicObject { private T _instance; private static Func<InvokeMemberBinder, IList<Type>> s_TypeArguments = null; private const string CLASS_NAME = "Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder"; public TransparentDynamicObject(T instance) { this._instance = instance; this.GetTypeArguments(); } private void GetTypeArguments() { if (s_TypeArguments == null) { var type = typeof(RuntimeBinderException).Assembly.GetTypes().Single( x => x.FullName == CLASS_NAME); var dynamicMethod = new DynamicMethod("@", typeof(IList<Type>), new[] { typeof(InvokeMemberBinder) }, true); //處理il var il = dynamicMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, type); il.Emit(OpCodes.Call, type.GetProperty("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder.TypeArguments", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).GetGetMethod(true)); il.Emit(OpCodes.Ret); //找出泛型參數 s_TypeArguments = (Func<InvokeMemberBinder, IList<Type>>) dynamicMethod.CreateDelegate(typeof(Func<InvokeMemberBinder, IList<Type>>)); } } public override bool TrySetMember(SetMemberBinder binder, object value) { var members = typeof(T).GetMember(binder.Name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); var member = members.FirstOrDefault(); if (member == null) throw new MissingMemberException(string.Format("Member '{0}' not found for type '{1}'", binder.Name, typeof(T))); if (member is PropertyInfo) { if (this._instance == null) { (member as PropertyInfo).SetValue(null, value); } else { (member as PropertyInfo).SetValue(this._instance, value); } return true; } if (member is FieldInfo) { if (this._instance == null) { (member as FieldInfo).SetValue(null, value); } else { (member as FieldInfo).SetValue(this._instance, value); } return true; } return false; } public override bool TryGetMember(GetMemberBinder binder, out object result) { var members = typeof(T).GetMember(binder.Name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); var member = members.FirstOrDefault(); if (member == null) throw new MissingMemberException(string.Format("Member '{0}' not found for type '{1}'", binder.Name, typeof(T))); if (member is PropertyInfo) { result = this._instance == null ? (member as PropertyInfo).GetValue(null, null) : (member as PropertyInfo).GetValue(this._instance, null); return true; } if (member is FieldInfo) { result = this._instance == null ? (member as FieldInfo).GetValue(null) : (member as FieldInfo).GetValue(this._instance); return true; } result = null; return false; } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { var method = typeof(T).GetMethod(binder.Name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); if (method == null) throw new MissingMemberException(string.Format("Method '{0}' not found for type '{1}'", binder.Name, typeof(T))); if (method.IsGenericMethod) { var typeArguments = s_TypeArguments(binder); if (typeArguments.Count > 0) method = method.MakeGenericMethod(typeArguments.ToArray()); } result = this._instance == null ? method.Invoke(null, args) : method.Invoke(this._instance, args); return true; } }
測試 TransparentDynamicObject<T>:
用戶端調用 AsTransparentDynamic 方法,就能可以改寫 dynamic 的行為,這時就能夠把非公開的成員拿出來用
PS.測試專案需要加 Microsoft.CSharp
public class AsTransparentDynamic { [TestMethod] public void 私有方法() { dynamic d = new Service().AsTransparentDynamic(); var actual = d.Func1(1); Assert.AreEqual(2, actual); } [TestMethod] public void 保護方法() { dynamic d = new Service().AsTransparentDynamic(); var actual = d.Func2(1); Assert.AreEqual(2, actual); } [TestMethod] public void 內部方法() { dynamic d = new Service().AsTransparentDynamic(); var actual = d.Func3(1); Assert.AreEqual(2, actual); } [TestMethod] public void 私有靜態方法() { dynamic d = new Service().AsTransparentDynamic(); var actual = d.Func4(1); Assert.AreEqual(2, actual); } [TestMethod] public void 私有泛型方法() { dynamic d = new Service().AsTransparentDynamic(); var actual = d.Func5<int>(5); Assert.AreEqual("5", actual); } [TestMethod] public void 私有屬性() { dynamic d = new Service().AsTransparentDynamic(); d.Prop = 5; var actual = d.Prop; Assert.AreEqual(5, actual); } }
實作 TransparentStaticDynamicObject:
接下來要處理靜態類別,必須要再另外處理,
PS.以下程式碼跟上面長的有夠像的,但其實有差異
{ private Type _target; private static Func<InvokeMemberBinder, IList<Type>> s_TypeArguments = null; private const string CLASS_NAME = "Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder"; public TransparentStaticDynamicObject(Type target) { this._target = target; this.GetTypeArguments(); } private void GetTypeArguments() { if (s_TypeArguments == null) { var type = typeof(RuntimeBinderException).Assembly.GetTypes().Single( x => x.FullName == CLASS_NAME); var dynamicMethod = new DynamicMethod("@", typeof(IList<Type>), new[] { typeof(InvokeMemberBinder) }, true); //處理il var il = dynamicMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, type); il.Emit(OpCodes.Call, type.GetProperty("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder.TypeArguments", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).GetGetMethod(true)); il.Emit(OpCodes.Ret); //找出泛型參數 s_TypeArguments = (Func<InvokeMemberBinder, IList<Type>>) dynamicMethod.CreateDelegate(typeof(Func<InvokeMemberBinder, IList<Type>>)); } } public override bool TrySetMember(SetMemberBinder binder, object value) { var members = this._target.GetMember(binder.Name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); var member = members.FirstOrDefault(); if (member == null) throw new MissingMemberException(string.Format("Member '{0}' not found for type '{1}'", binder.Name, this._target)); if (member is PropertyInfo) { if (this._target == null) { (member as PropertyInfo).SetValue(null, value); } else { (member as PropertyInfo).SetValue(this._target, value); } return true; } if (member is FieldInfo) { if (this._target == null) { (member as FieldInfo).SetValue(null, value); } else { (member as FieldInfo).SetValue(this._target, value); } return true; } return false; } public override bool TryGetMember(GetMemberBinder binder, out object result) { var members = this._target.GetMember(binder.Name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); var member = members.FirstOrDefault(); if (member == null) throw new MissingMemberException(string.Format("Member '{0}' not found for type '{1}'", binder.Name, this._target)); if (member is PropertyInfo) { result = this._target == null ? (member as PropertyInfo).GetValue(null, null) : (member as PropertyInfo).GetValue(this._target, null); return true; } if (member is FieldInfo) { result = this._target == null ? (member as FieldInfo).GetValue(null) : (member as FieldInfo).GetValue(this._target); return true; } result = null; return false; } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { var method = this._target.GetMethod(binder.Name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); if (method == null) throw new MissingMemberException(string.Format("Method '{0}' not found for type '{1}'", binder.Name, this._target)); if (method.IsGenericMethod) { var typeArguments = s_TypeArguments(binder); if (typeArguments.Count > 0) method = method.MakeGenericMethod(typeArguments.ToArray()); } if (this._target == null) { result = method.Invoke(null, args); } else { result = method.Invoke(this._target, args); } return true; } }
測試 TransparentStaticDynamicObject:
public void 靜態類別_私有方法() { dynamic d = new TransparentStaticDynamicObject(typeof(Foo)); var actual = d.Func1(1); Assert.AreEqual(2, actual); } [TestMethod] public void 靜態類別_私有泛型方法() { dynamic d = new TransparentStaticDynamicObject(typeof(Foo)); var actual = d.Func2<int>(1); Assert.AreEqual("1", actual); }
文章出自:http://www.dotblogs.com.tw/yc421206/archive/2014/08/12/146240.aspx
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET