[C#.NET] 利用 dynamic 簡化反射程式碼? 實作 DynamicObject

  • 5009
  • 0
  • C#
  • 2015-12-28

[C#.NET] 利用 dynamic 簡化反射程式碼? 實作 DynamicObject

http://www.dotblogs.com.tw/yc421206/archive/2012/10/31/79794.aspx

續上篇,dynamic 在處理反射公開方法時可以讓我們省掉一些程式碼,不過在處理非公開方法,討不太到便宜

預設,dynamic  只會處理公開成員,我想要它也能處理非公開方法,以便我處理單元測試。

本文章節:

準備待測類別及方法:

dynamic 處理非公開方法:

實作 TransparentDynamicObject<T>:

測試 TransparentDynamicObject<T>:

實作 TransparentStaticDynamicObject:

測試 TransparentStaticDynamicObject:


準備待測類別及方法:

開始前要有測試類別

{
    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();
    }
}

dynamic 處理非公開方法:

下面的寫法,應該不莫生了

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,但它並不是公開類別,沒有它會導致調用泛型方法失敗

解法,可以參考下篇:

http://stackoverflow.com/questions/6954069/how-can-i-handle-generic-method-invocations-in-my-dynamicobject

 


實作 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

Image result for microsoft+mvp+logo