• OnLoad与Page_Load的差异分析


    记得最开始学习ASP.NET的时候,我们就被告知:Page_Load方法里面可以写页面加载的代码。

    于是我们就懵懵懂懂写了很长时间的Page_Load方法。最近回过头思考,为什么一个普通的方法,

    能被自动调用呢?于是就得知了AutoEventWireup属性。

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="Default" %>

    一般我们新建页面的时候,AutoEventWireup就为true。MSDN的解释是:指示控件的事件是否自动匹配 (Autowire)。

    如果启用事件自动匹配,则为 true;否则为 false。默认值为 true

    那么我们先得到一个结论是:AutoEventWireup为true时,Page_Load、Page_Init之类的方法名

    能被自动调用。

    下面我们反编译源代码来看看里面是怎么回事。首先反编译所有页面的父类:Page类。

    public class Page : TemplateControl, IHttpHandler { }
    

    大致浏览一下,没有找到“Page_Load” 之类的字符串,说明不是在Page类处理的,继续查找Page类

    的父类TemplateControl类。

    public abstract class TemplateControl : Control, INamingContainer, IFilterResolutionService
    {
        // Fields
        private static object _emptyEventSingleton;
        private static Hashtable _eventListCache;
        private static IDictionary _eventObjects;
        private static object _lockObject;
        private int _maxResourceOffset;
        private BuildResultNoCompileTemplateControl _noCompileBuildResult;
        private const string _onTransactionAbortEventName = "OnTransactionAbort";
        private const string _onTransactionCommitEventName = "OnTransactionCommit";
        private const string _pageAbortTransactionEventName = "Page_AbortTransaction";
        private const string _pageCommitTransactionEventName = "Page_CommitTransaction";
        private const string _pageDataBindEventName = "Page_DataBind";
        private const string _pageErrorEventName = "Page_Error";
        private const string _pageInitCompleteEventName = "Page_InitComplete";
        private const string _pageInitEventName = "Page_Init";
        private const string _pageLoadCompleteEventName = "Page_LoadComplete";
        private const string _pageLoadEventName = "Page_Load";
        private const string _pagePreInitEventName = "Page_PreInit";
        private const string _pagePreLoadEventName = "Page_PreLoad";
        private const string _pagePreRenderCompleteEventName = "Page_PreRenderComplete";
        private const string _pagePreRenderEventName = "Page_PreRender";
        private const string _pageSaveStateCompleteEventName = "Page_SaveStateComplete";
        private const string _pageUnloadEventName = "Page_Unload";
    。。。。。。。。
    }
    

    找到了!里面黑茫茫一片的字符串,呵呵。继续仔细查找入口,发现了如下方法:

    代码
    internal void HookUpAutomaticHandlers()
    {
    //指示是否支持自动事件。SupportAutoEvents属性是只读的,并且在所有情况下都为 true
    if (this.SupportAutoEvents)
    {
    object obj2 = _eventListCache[base.GetType()];
    IDictionary dictionary
    = null;
    if (obj2 == null)
    {
    lock (_lockObject)
    {
    obj2
    = _eventListCache[base.GetType()];
    if (obj2 == null)
    {
    dictionary
    = new ListDictionary();
    //GetDelegateInformation将匹配的方法加到字典中
    this.GetDelegateInformation(dictionary);
    if (dictionary.Count == 0)
    {
    obj2
    = _emptyEventSingleton;
    }
    else
    {
    obj2
    = dictionary;
    }
    _eventListCache[
    base.GetType()] = obj2;
    }
    }
    }
    //这里将找到的类似Page_Load这些方法与Page.Load这些事件指定的方法比对
    //将没有重复的添加到事件中
    if (obj2 != _emptyEventSingleton)
    {
    dictionary
    = (IDictionary) obj2;
    foreach (string str in dictionary.Keys)
    {
    EventMethodInfo info
    = (EventMethodInfo) dictionary[str];
    bool flag = false;
    MethodInfo methodInfo
    = info.MethodInfo;
    Delegate delegate2
    = base.Events[_eventObjects[str]];
    if (delegate2 != null)
    {
    foreach (Delegate delegate3 in delegate2.GetInvocationList())
    {
    if (delegate3.Method.Equals(methodInfo))
    {
    flag
    = true;
    break;
    }
    }
    }
    if (!flag)
    {
    IntPtr functionPointer
    = methodInfo.MethodHandle.GetFunctionPointer();
    EventHandler handler
    = new CalliEventHandlerDelegateProxy(this, functionPointer, info.IsArgless).Handler;
    base.Events.AddHandler(_eventObjects[str], handler);
    }
    }
    }
    }
    }

    上面的方法黑压压一片,归纳起来就是2点:查找页面上Page_Load方法,添加到一个字典中,

    再与Page.Load事件进行比对,将不重复的方法添加到Page.Load事件。也就是说如果页面上

    有Page_Load方法,并且Page.Load+=new EventHandler(Page_Load);为Page.Load添加

    了委托方法,那么Page_Load方法只会执行一次。

    但是 HookUpAutomaticHandlers()方法是由谁来调用的?AutoEventWireup属性又在什么地方

    用到了? 这点我也还没弄懂,推测是在ASP.NET的页面生命周期中,由Page之前的模块(比如HttpHandler

    或者HttpModule)来判断AutoEventWireup的值,如果为true则调用HookUpAutomaticHandlers()方法。

    参考:.NET (C#) Internals: ASP.NET 应用程序与页面生命周期

     接下我们来看看TemplateControl.GetDelegateInformation方法

    private void GetDelegateInformation(IDictionary dictionary)
    {
        if (HttpRuntime.IsFullTrust)
        {
            this.GetDelegateInformationWithNoAssert(dictionary);
        }
        else
        {
            this.GetDelegateInformationWithAssert(dictionary);
        }
    }
    

    进一步查看

    private void GetDelegateInformationWithAssert(IDictionary dictionary)
    {
        this.GetDelegateInformationWithNoAssert(dictionary);
    }
    

    那么关键就在TemplateControl.GetDelegateInformationWithNoAssert方法了:

    private void GetDelegateInformationWithNoAssert(IDictionary dictionary)
    {
        if (this is Page)
        {
            this.GetDelegateInformationFromMethod("Page_PreInit", dictionary);
            this.GetDelegateInformationFromMethod("Page_PreLoad", dictionary);
            this.GetDelegateInformationFromMethod("Page_LoadComplete", dictionary);
            this.GetDelegateInformationFromMethod("Page_PreRenderComplete", dictionary);
            this.GetDelegateInformationFromMethod("Page_InitComplete", dictionary);
            this.GetDelegateInformationFromMethod("Page_SaveStateComplete", dictionary);
        }
        this.GetDelegateInformationFromMethod("Page_Init", dictionary);
        this.GetDelegateInformationFromMethod("Page_Load", dictionary);
        this.GetDelegateInformationFromMethod("Page_DataBind", dictionary);
        this.GetDelegateInformationFromMethod("Page_PreRender", dictionary);
        this.GetDelegateInformationFromMethod("Page_Unload", dictionary);
        this.GetDelegateInformationFromMethod("Page_Error", dictionary);
        if (!this.GetDelegateInformationFromMethod("Page_AbortTransaction", dictionary))
        {
            this.GetDelegateInformationFromMethod("OnTransactionAbort", dictionary);
        }
        if (!this.GetDelegateInformationFromMethod("Page_CommitTransaction", dictionary))
        {
            this.GetDelegateInformationFromMethod("OnTransactionCommit", dictionary);
        }
    }
    

    又看到了熟悉的"Page_Load"字符串。GetDelegateInformationFromMethod光看方法名

    应该就能猜到它的作用是去查找页面上指定名称的方法:

    private bool GetDelegateInformationFromMethod(string methodName, IDictionary dictionary)
    {
        EventHandler handler = (EventHandler) Delegate.CreateDelegate(typeof(EventHandler), this, methodName, true, false);
        if (handler != null)
        {
            dictionary[methodName] = new EventMethodInfo(handler.Method, false);
            return true;
        }
        VoidMethod method = (VoidMethod) Delegate.CreateDelegate(typeof(VoidMethod), this, methodName, true, false);
        if (method != null)
        {
            dictionary[methodName] = new EventMethodInfo(method.Method, true);
            return true;
        }
        return false;
    }
    

    上面的代码的作用是:以不论大小写的方式查找指定名称的方法,如果找到带参数的则添加到字典

    中,然后返回。如果找不到带参数的,则查找无参的指定名称的方法,找到了添加到字典中。

    带参数的方法签名必须为:Page_Load(object sender, EventArgs e)

    无参的方法签名必须为:Page_Load()

    也就是说,Page_Load不分大小写,可以写成Page_loAd,同时存在带参数的和无参的,只会取带参数的。

    没有带参数的时候才会去取无参的。如果同时存在名称分别为Page_Load与Page_loAd两个带参(或者都

    是无参)方法,那么取写在后面的方法(就是在代码中谁写在后面就取谁)。

    Page_Load的执行时间是在Control类(TemplateControl类的父类)执行完OnLoad方法后执行。

    页面上的OnLoad其实是重载父类的OnLoad方法,利用多态去执行,从效率上来说自然比较Page_Load

    那种利用事件去加载的形式要高,所以微软的某篇文档(地址忘记了)中说:如果要考虑效率,则

    AutoEventWireup始终设置为false。

    下面用几个例子来证明上面的结论:(AutoEventWireup都设置为true)

    例子一:

    public partial class Default : Page
    {
        protected void Page_LoaD(object sender, EventArgs e)
        {
            Response.Write("3");
        }
    
        protected void page_LoaD(object sender, EventArgs e)
        {
            Response.Write("2");
        }
    
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Write("1");
        }
    }
    
    输出1,因为Page_Load方法不分大小写,
    多个带参的Page_Load方法只取最后一个

    例子二:

    public partial class Default : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Write("1");
        }
    
        protected void Page_Load()
        {
            Response.Write("2");
        }
    }
    
    输出1,因为如果存在带参的Page_Load,就不去管无参的了

    例子三:

    public partial class Default : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Write("1");
        }
    
        public Default() 
        {
            Page.Load += new EventHandler(Page_Load);    
        }
    }
    
    输出1,因为重复的方法是不会添加到Load事件的委托链中
    所以只会执行1次

     例子四:

    public partial class Default : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Write("1");
        }
    
        protected void Page_LoaD(object sender, EventArgs e)
        {
            Response.Write("2");
        }
    
        public Default() 
        {
            Page.Load += new EventHandler(Page_Load);    
        }
    }
    
    输出12,这里注意委托链里面方法的顺序,先在构造函数中加了Page_Load方法,
    然后查找匹配Page_Load名字的方法,找到了Page_LoaD(因为它写在后面),
    接着查找是否有重复的,查找结果是没有,于是将Page_LoaD加到委托链中

     例子五: 

    public partial class Default : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Write("2");
        }
    
        protected override void OnLoad(EventArgs e)
        {
            Response.Write("1");
            base.OnLoad(e);
            Response.Write("3");
        }
    }
    
    输出123。首先由于override父类的OnLoad,所以先执行页面的OnLoad方法,
    输出1,然后执行父类的OnLoad方法,一直上推到执行完Control类的OnLoad
    方法后,执行Load事件的委托链方法,执行Page_Load方法,输出2。最后回到
    页面的OnLoad方法输出3

    结论: AutoEventWireup为true时,里面的一些执行规则很奇特,比如Page_Load方法可以不分大小写之类的,

    这些都是反编译以后才发现的,MSDN里面貌似都找不到相应的解释。而且如果页面继承MyBasePage类,MyBasePage类

    继承Page类,页面与MyBasePage类中都有Page_Load方法,出现的情况更复杂(比如MyBasePage类的Page_Load方法

    加不加virtual关键字,运行的结果都可能会不一样),这样反而会影响开发者的逻辑,增加开发的复杂度。同时

    事件机制效率相对较低,因此建议将AutoEventWireup设为false,只用override OnLoad的方式,这样尽可能将一切都

     控制在开发者手中。(以上结论对Page_Init()等方法都是一样的)

    我的网站:http://i.goto327.top:85/
  • 相关阅读:
    vim常用命令
    转:CRF++总结1
    转:CRF++总结2
    并查集算法程序
    CRF++使用小结(转)
    并查集算法程序
    C#winform 画图
    转:字符识别
    转:A Survey On Relation Extraction
    转:生产计划问题
  • 原文地址:https://www.cnblogs.com/jintianhu/p/1902078.html
Copyright © 2020-2023  润新知