Hello World
Spiga

检查几个程序集中的internal成员

2009-09-13 23:17 by 老赵, 12475 visits

两个星期前我写了一篇文章谈到一个现象(或是感觉):我发现“类中的internal成员可能是一种坏味道”,原因在于违反了“单一职责”原则。然后谈了一般情况下遇到这种情况时一种可用的重构方式之一。结果自然是有人同意有人反对。不过刚才我忽然想到,不如检查一下微软的框架中internal成员的情况吧。微软最近几个框架都公开的源代码,社区反响不错,应该较为值得参考。

首先我准备了这样一段代码:

foreach (var m in Detect(typeof(Route).Assembly))
{
    Console.WriteLine(m.DeclaringType.Name + " - " + m.Name);
}

这段代码会输出所有满足条件的方法,条件有三项:

  1. 属于公开类(即GetExprotedTypes方法获得的类型)。
  2. 直接定义在类中的方法(而不是通过继承得来的)。
  3. 修饰符为internal(即代码中的IsAssembly条件)。

首先,我们来查看ASP.NET Routing(System.Web.Routing.dll)中的internal成员:

foreach (var m in Detect(typeof(Route).Assembly))
{
    Console.WriteLine(m.Name);
}

其结果是:

RequestContext - set_HttpContext
RequestContext - set_RouteData

也就是说,除了RequestContext中的两个属性的set方法,没有其他internal的成员。但是从.NET Reflector的代码分析来看,这两个方法根本没有被RequestContext以外的类型使用过:

也就是说,即使我们把这两个成员设为private也没有任何问题。

接下来看一个大一些的框架:ASP.NET MVC(System.Web.Mvc.dll)。结果如下:

ValidateAntiForgeryTokenAttribute - get_Serializer
ValidateAntiForgeryTokenAttribute - set_Serializer
AjaxOptions - ToJavascriptString
HtmlHelper - get_Serializer
HtmlHelper - set_Serializer
HtmlHelper - EvalString
HtmlHelper - EvalBoolean
HtmlHelper - GetModelStateValue
HtmlHelper - RenderPartialInternal
ValueProviderDictionary - get_Dictionary
ReflectedActionDescriptor - get_DispatcherCache
ReflectedActionDescriptor - set_DispatcherCache
DefaultModelBinder - BindComplexElementalModel
DefaultModelBinder - BindComplexModel
DefaultModelBinder - BindSimpleModel
DefaultModelBinder - UpdateCollection
DefaultModelBinder - UpdateDictionary
OutputCacheAttribute - get_CacheSettings
Controller - get_RouteCollection
Controller - set_RouteCollection
ControllerActionInvoker - get_DescriptorCache
ControllerActionInvoker - set_DescriptorCache
MultiSelectList - GetListItems
RedirectToRouteResult - get_Routes
RedirectToRouteResult - set_Routes
DefaultControllerFactory - get_BuildManager
DefaultControllerFactory - set_BuildManager
DefaultControllerFactory - get_ControllerBuilder
DefaultControllerFactory - set_ControllerBuilder
DefaultControllerFactory - get_ControllerTypeCache
DefaultControllerFactory - set_ControllerTypeCache
MvcHandler - get_ControllerBuilder
MvcHandler - set_ControllerBuilder
ViewMasterPage - get_ViewPage
ViewUserControl - get_ViewPage
WebFormView - get_BuildManager
WebFormView - set_BuildManager
WebFormViewEngine - get_BuildManager
WebFormViewEngine - set_BuildManager

哗,好长一个列表。不过看到这里我觉得似乎有些不太对劲儿,为什么基本上都是些属性?因为一般来说,属性不代表一个类的“行为”,所以我打算忽略掉所有的属性。因此,我把Detect方法修改为:

public static IEnumerable<MethodInfo> Detect(Assembly assembly)
{
    foreach (var type in assembly.GetExportedTypes())
    {
        var methods = type.GetMethods(
            BindingFlags.Instance |
            BindingFlags.NonPublic |
            BindingFlags.InvokeMethod |
            BindingFlags.DeclaredOnly);

        foreach (var m in methods)
        {
            if (m.IsAssembly && !m.IsSpecialName)
            {
                yield return m;
            }
        }
    }
}

于是剩下的方法还有:

AjaxOptions - ToJavascriptString
HtmlHelper - EvalString
HtmlHelper - EvalBoolean
HtmlHelper - GetModelStateValue
HtmlHelper - RenderPartialInternal
DefaultModelBinder - BindComplexElementalModel
DefaultModelBinder - BindComplexModel
DefaultModelBinder - BindSimpleModel
DefaultModelBinder - UpdateCollection
DefaultModelBinder - UpdateDictionary
MultiSelectList - GetListItems

再对ASP.NET AJAX(System.Web.Extensions.dll)运行一下:

DataPager - GetQueryStringNavigateUrl
DataPagerField - SetDirty
DataPagerField - SetDataPager
LinqDataSourceView - ReleaseSelectContexts
ListViewDeletedEventArgs - SetKeys
ListViewDeletedEventArgs - SetValues
ListViewInsertedEventArgs - SetValues
ListViewUpdatedEventArgs - SetKeys
ListViewUpdatedEventArgs - SetNewValues
ListViewUpdatedEventArgs - SetOldValues
ConvertersCollection - CreateConverters
JavaScriptSerializer - ConverterExistsForType
JavaScriptSerializer - Serialize
UpdatePanelTrigger - SetOwner
ScriptDescriptor - RegisterDisposeForDescriptor
ScriptComponentDescriptor - RegisterDisposeForDescriptor
ScriptManager - AddFrameworkScripts
ScriptManager - AddScriptCollections
ScriptManager - CreateUniqueScriptKey
ScriptManager - GetScriptResourceUrl
ScriptManager - RegisterClientScriptBlockInternal
ScriptManager - RegisterClientScriptIncludeInternal
ScriptManager - RegisterStartupScriptInternal
ScriptManagerProxy - CollectScripts
ScriptManagerProxy - RegisterServices
ScriptReference - DetermineCulture
ScriptReference - GetAssembly
ScriptReference - GetPath
ScriptReference - GetResourceName
ScriptReference - ShouldUseDebugScript
ServiceReference - Register
UpdatePanel - ClearContent
UpdatePanel - SetAsyncPostBackMode
UpdatePanelTriggerCollection - HasTriggered
UpdatePanelTriggerCollection - Initialize

我从这些数据中得出的结论是:

  • public类中的internal方法的确不多。
  • 使用internal来修饰属性是常见做法。
  • 一个方法是否属于某个类,由职责确定。
  • 如果某个方法不需要对外公开,则使用internal来修饰。

您的看法如何?

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

19 条回复

  1. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-09-13 23:49:00

    佩服你的执着和对技术的追求

  2. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-09-13 23:49:00

    老赵你还真有着闲心啊

  3. 老赵
    admin
    链接

    老赵 2009-09-14 00:06:00

    @温景良(Jason)
    想到了就去做,不需要多少时间的。

  4. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-09-14 00:20:00

    结论完全同意,基本和自己平时的Coding时的准则一致。不过说实话,我更愿意相信自己的smell和更常规的设计规范。另外,建议大家不要太在意去学习.net framework库中的非public类或方法等的内部实现。据我所知,很多内部使用的类,其实都是实习生写的。

  5. 老赵
    admin
    链接

    老赵 2009-09-14 00:28:00

    @Teddy's Knowledge Base
    这个说法似乎有点过了。其实实习生本来也就只占一点点数量,说10%都嫌多。而且有code review过程很多东西还是把握得了的。
    而且,微软现在公开的代码也越来越多,像asp.net mvc,asp.net ajax都是公开代码的,不敢大意。
    我相信asp.net mvc和asp.net ajax的代码质量,因为平时也关注他们开发人员,也关注独立社区用户对框架的评价。
    当然,我完全同意不用把它们当“圣经”,肯定要用“批判性眼光”。但我相信.NET framework在觉大部分情况下还是靠得住的。
    还有,毕竟微软也搞得出一本技惊四座的Framework Design,不是闹着玩的,呵呵。

  6. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-09-14 09:23:00

    不能完全拿来,批判的吸收。

  7. 老赵
    admin
    链接

    老赵 2009-09-14 09:28:00

    @DiggingDeeply
    这是一定的。

  8. 水木
    *.*.*.*
    链接

    水木 2009-09-14 10:08:00

    想问一下,
    InternalMembers. 是在哪个命名空间下呀,

  9. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-09-14 10:13:00

    DiggingDeeply:不能完全拿来,批判的吸收。



    我觉得最应该吸收的是这种“凑合能用”的思想。

    最好再找找有多少东西是因为类型是internal的所以必须是internal而不能是public的。当然从语义上来说,这两者应该是统一的,不应该是因为类型是internal而逼迫成员internal。而是internal的类型本应只被internal的成员使用。

  10. 老赵
    admin
    链接

    老赵 2009-09-14 10:13:00

    @水木
    改了,就是Detect方法,没有InteralMembers这东西,呵呵。

  11. 老赵
    admin
    链接

    老赵 2009-09-14 10:16:00

    @Ivony...
    就是找一下internal类型中的public/internal方法?

  12. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-09-14 10:18:00

    Jeffrey Zhao:
    @Ivony...
    就是找一下internal类型中的public/internal方法?



    比如说有某个方法,它的某个参数或者返回值的“类型”是internal的,那么这个方法就只能是internal或private的,而不能是public或protected的。

    属性同理。


    就是这种方法和属性,可以理解为被类型逼迫必须是internal。


    所以我做设计的时候有时候也遵循一个原则就是internal的类型应该有public的基类型(含接口)。

  13. 老赵
    admin
    链接

    老赵 2009-09-14 11:12:00

    @Ivony...
    原来如此……

  14. 道法自然
    *.*.*.*
    链接

    道法自然 2009-09-14 11:23:00

    微软的Framwork Design Guidline非常的不错,值得学习。我觉得,Internal和Public的区分是从用户角度来看的,对类库或框架来讲,如何划分Internal和Public/Protected非常重要,在一般应用中,他们区别就比较次要的。

    对于我来讲,所有API的设计都是以场景驱动原则进行设计的,从用户的角度设计一个类库或框架如何的被用户使用。从易用性角度,所有与用户需要功能无关的类最好都是Internal,也就是说,支持这些公共API的实现都应该是Internal;不过,考虑到扩展性,为了方便用户对类库或框架的扩展,降低扩展难度,又需要将部分Internal实现变为Public。因此,Public和Internal的使用取决于类库或框架所提供的功能。

  15. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-09-14 11:45:00

    道法自然:
    微软的Framwork Design Guidline非常的不错,值得学习。我觉得,Internal和Public的区分是从用户角度来看的,对类库或框架来讲,如何划分Internal和Public/Protected非常重要,在一般应用中,他们区别就比较次要的。

    对于我来讲,所有API的设计都是以场景驱动原则进行设计的,从用户的角度设计一个类库或框架如何的被用户使用。从易用性角度,所有与用户需要功能无关的类最好都是Internal,也就是说,支持这些公共API的实现都应该是Internal;不过,考虑到扩展性,为了方便用户对类库或框架的扩展,降低扩展难度,又需要将部分Internal实现变为Public。因此,Public和Internal的使用取决于类库或框架所提供的功能。



    嗯,是这样的。

    但其实深入进去,会发现这里面有几个好玩的问题。

    诚如上言,所有与用户需要功能无关的类最好都要是internal的。但作为框架类库而言,哪些功能是用户不需要的呢?大多数情况下,这些都会是一些应用于特定场景的逻辑。但作为设计框架而言,就应该将这些应用于特定场景的逻辑抽象成具有普适性的逻辑而公开。但这里会陷入没完没了的泥潭。。。。

    先吃饭,回头再聊。。。

  16. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-14 12:02:00

    Ivony...:

    Jeffrey Zhao:
    @Ivony...
    就是找一下internal类型中的public/internal方法?



    比如说有某个方法,它的某个参数或者返回值的“类型”是internal的,那么这个方法就只能是internal或private的,而不能是public或protected的。

    属性同理。


    就是这种方法和属性,可以理解为被类型逼迫必须是internal。


    所以我做设计的时候有时候也遵循一个原则就是internal的类型应该有public的基类型(含接口)。


    good原则

  17. 道法自然
    *.*.*.*
    链接

    道法自然 2009-09-14 12:18:00

    @Ivony...

    程序设计中,原则问题是原则问题,但充满了各种无奈的妥协......

  18. Colin Han
    *.*.*.*
    链接

    Colin Han 2009-09-14 23:47:00

    呵呵,我觉的老赵的结论有所偏颇。因为你在拿开源软件试和基础库说事。因为开源软件天生是设计来给用户改的,基础库天生是设计来让用户定制的。着两个在合在一起,自然internal成员就很少了。

    呵呵,我不同意你的论据。但是,基本同意你的结论。Internal成员可能确实意味着坏味道,出现的时候,我们真的应该仔细吸吸鼻子,考虑下是否需要重构。

  19. 老赵
    admin
    链接

    老赵 2009-09-15 09:29:00

    @Colin Han
    那么应该怎么来“检验”呢?还是说,这种检验的做法本来就不太合适?

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我