OnLoad與Page_Load的差異分析

作者: 小小娟  來源: 博客園  發布時間: 2011-01-27 10:47  閱讀: 4936 次  推薦: 6   原文鏈接   [收藏]  
摘要:AutoEventWireup為true時,里面的一些執行規則很奇特,比如Page_Load方法可以不分大小寫之類的,這些都是反編譯以后才發現的,MSDN里面貌似都找不到相應的解釋。

  記得最開始學習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()等方法都是一樣的)

6
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()