c# dynamic 类型调用静态方法实例

 

/玄魂

背景

最近一直在和同事讨论单元测试的问题,在对已有代码的可测试性进行评估的时候,我们发现业务逻辑层和持久层的测试分离成为了难点。

正常而言,对业务逻辑的单元测试是要同持久层分离开的。为了确保业务逻辑层的可测试性,要求业务逻辑层依赖持久层的接口而不是实现,这样在进行单元测试的时候,可以灵活的使用Mock和数据库来填充数据。

但是我们的代码规范规定,Dao层的方法必须是静态方法,而且之前的业务逻辑代码在逻辑内部调用Dao,二者紧紧的耦合在一起。现在面临的问题是Dao层的方法必须是静态方法,我们没有办法提取接口。初步讨论,为了达到可测试性,有以下几个改造方案:

l  将业务逻辑层调用的Dao类提取出公有变量,然后调用方实施属性注入或者构造函数注入的方式。实现了业务逻辑的可测试性,但是没有实现业务逻辑和持久层的解耦。

l  新建接口封装对Dao的调用。实现了可测试性,也实现了业务逻辑和持久层的解耦,但是违反了Dao方法必须静态的初衷,如果不考虑单元测试,接口包装的方式在设计上显得臃肿,而且改造起来代码量大。

l  使用dynamic类型声明Dao类,在业务逻辑的构造工厂中依赖注入实现。这种方法对代码的改动量最小,但是失去了编译时检查的优势;同时每种调用类型都是事先知道的,不是dynamic类型的标准应用;这里使用dynamic类型只是利用它的运行时绑定的特性,实现类似接口的功能,不得已而为之。

总之,如果不实现真正的解耦,任何方案都是勉强。本篇文章讨论上述最后一种方案的实施过程中遇到的一个dynamic 类型变量调用静态方法的解决方案,同时兼顾单元测试,和分层解耦。这种方案也不是我的原创,参考链接:http://blogs.msdn.com/b/davidebb/archive/2009/10/23/using-c-dynamic-to-call-static-members.aspx

dynamic 类型调用静态方法

我先模拟一个Dao的实现,类名为ReportItemDao,只有一个方法,名为GetItemDescriptionAndCode,如下:

public class ReportItemDao

    {

    /// <summary>

        /// 根据条目ID获取条目描述列表

        /// </summary>

        /// <param name="itemID">条目ID</param>

        /// <returns>当前条目的描述列表</returns>

        /// <remarks>玄魂-2012-1-11创建</remarks>

        public static Dictionary<string, string> GetItemDescriptionAndCode(Guid itemID)

        {

            Dictionary<string, string> itemDesList = new Dictionary<string, string>();

            Database database = Database.GetDatabase(StaticParameters.CONNECTIONSTRINGS_NAME_Design);

            SafeProcedure.ExecuteAndGetInstanceList(database, @"[dbo].[GetReportTempDesByItemID]",

                                                parameters =>

                                                {

                                                    parameters.AddWithValue(@"itemID", itemID);

                                                },

                                                (IRecord record, int entity) =>

                                                {

                                                    itemDesList.Add(record.Get<string>(@"Code"), record.Get<string>(@"ReportItemDecription"));

                                                }

                                                );

            return itemDesList;

        }

}

再模拟一个业务逻辑层的代码,调用上面的方法,如下:

public class ReportItemProvider : IReportItemProvider

    {

 

public Dictionary<string, string> GetItemDescriptionAndCode(Guid itemID)

        {

            return ReportItemDao.GetItemDescriptionAndCode(itemID);

        }

}

上面的调用代码也很简单,没有任何逻辑,实际场景下会复杂得多。首先,在ReportItemProvider类声明一个dynamic字段,名为ReportItemDao,取消对ReportItemDao类的命名空间的引入。修改之后的代码如下:

public class ReportItemProvider : IReportItemProvider

{

dynamic ReportItemDaoDynamic

 

public Dictionary<string, string> GetItemDescriptionAndCode(Guid itemID)

        {

            return ReportItemDaoDynamic.GetItemDescriptionAndCode(itemID);

        }

}

上面的代码是理想状态,并不能运行成功,因为我们无法将ReportItemDao类赋值给dynamic类型,实例化的类型是无法调用静态方法的。

ReportItemDao类的实例不能赋值给ReportItemDaoDynamic,那我们只能传一个ReportItemDaoType类实例给ReportItemDaoDynamic,别无他法。传递一个Type类实例和dynamic类型,意味着在执行具体方法时必须执行反射,但是dynamic类型目前还不支持这样的调用,我们必须对它的调用过程进行重写。

下面的代码实现了对dynamic类型的自定义。先创建一个名为StaticMembersDynamicWrapper的类,继承自DynamicObject类,然后重写它的TryGetMemberTryInvokeMember方法,利用反射找到静态方法并执行。

public class StaticMembersDynamicWrapper : DynamicObject {

private Type _type;

public StaticMembersDynamicWrapper(Type type) { _type = type; }

// Handle static properties

public override bool TryGetMember(GetMemberBinder binder, outobjectresult) {

      PropertyInfo prop = _type.GetProperty(binder.Name, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public);

      if(prop == null) {

                       result = null;

                       returnfalse;

                      }

       result = prop.GetValue(null, null);

       returntrue;

}

// Handle static methods

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, outobjectresult) {

        MethodInfo method = _type.GetMethod(binder.Name, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public);

        if(method == null) {

                            result = null;

                             returnfalse;

                            }

       result = method.Invoke(null, args);

       returntrue;

     }

}

dynamic 类型调用静态方法的问题解决了,还需要一个对象工厂对dynamic 类型的变量进行依赖注入。

在依赖注入之前,我还要一个简单的Ioc容器来存储Dao类的Type,代码如下:

 public  static class DaoContainer

    {

        private static Dictionary<string, Type> daoDic = new Dictionary<string, Type >();

        static DaoContainer ()

        {

            daoDic.Add(“ReportItemDaoDynamic”,typeOf(ReportItemDao));        

        }

 

  public static Type  GetTypeInstancestring key

{

  return daoDic[key];

}

}

下面我们用一个简单的工厂类来创建ReportItemProvider

 class ProviderFactory

    {

      public T GetInstance<T>() where T : class

        {

       var instance = ReportItemProvider.Instance;//单例

            SetDao(instance);

                      return instance;

        }

}

private void SetDao(object obj)

{

   Type type = obj.GetType();

           FieldInfo[]fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

           foreach (FieldInfo field in fields)

           {

               if (field.GetValue(obj)==null)

               {

//这里判断是否应该赋值,省略……

                   string name = field.Name;

                 field.SetValue(obj, DaoContainer. GetTypeInstance(name));

               }

           }

}

ok,目前为止我已经给出了一个极其简单但是五脏俱全的例子了,当然这可能不是最好的解决方案。希望对您能有所帮助。


作者:玄魂
出处:http://www.cnblogs.com/xuanhun/
原文链接:http://www.cnblogs.com/xuanhun/ 更多内容,请访问我的个人站点 对编程,安全感兴趣的,加qq群:hacking-1群:303242737,hacking-2群:147098303,nw.js,electron交流群 313717550。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
关注我:关注玄魂的微信公众号

原文地址:https://www.cnblogs.com/xuanhun/p/2319810.html