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类。
1 | public class Page : TemplateControl, IHttpHandler { } |
大致浏览一下,没有找到“Page_Load” 之类的字符串,说明不是在Page类处理的,继续查找Page类
的父类TemplateControl类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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方法
1 2 3 4 5 6 7 8 9 10 11 | private void GetDelegateInformation(IDictionary dictionary) { if (HttpRuntime.IsFullTrust) { this .GetDelegateInformationWithNoAssert(dictionary); } else { this .GetDelegateInformationWithAssert(dictionary); } } |
进一步查看
1 2 3 4 | private void GetDelegateInformationWithAssert(IDictionary dictionary) { this .GetDelegateInformationWithNoAssert(dictionary); } |
那么关键就在TemplateControl.GetDelegateInformationWithNoAssert方法了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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光看方法名
应该就能猜到它的作用是去查找页面上指定名称的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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)
例子一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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方法只取最后一个
例子二:
1 2 3 4 5 6 7 8 9 10 11 12 | 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,就不去管无参的了
例子三:
1 2 3 4 5 6 7 8 9 10 11 12 | 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次
例子四:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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加到委托链中
例子五:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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()等方法都是一样的)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?