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"); } }
多個帶參的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"); } }
例子三:
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次
例子四:
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); } }
然后查找匹配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"); } }
輸出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()等方法都是一樣的)