代码改变世界

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

2009-09-13 23:17  Jeffrey Zhao  阅读(11959)  评论(19编辑  收藏

两个星期前我写了一篇文章谈到一个现象(或是感觉):我发现“类中的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来修饰。

您的看法如何?