記得最開始學習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 vo上海網站建設id 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 Pag上海企業網站設計與製作e_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()等方法都是一樣的)