首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

Emit 實戰:為動態類型定義事件與 Add/Remove 存取器

     編輯:關於.NET

在 Reflection.Emit 中為動態類型添加事件,比屬性/方法複雜得多:必須手動實現 add_EventName / remove_EventName 兩個存取器,且底層依賴一個同名私有委托字段。本文拆解完整流程,含 Delegate.Combine/Remove 的正確用法與 EventBuilder 綁定細節。

目標 C# 事件模式

public event EventHandler<RandGeneratedEventArgs> RandGenerated;

protected virtual void OnRandGenerated(int rand)
{
    RaiseRandGeneratedEvent(rand);
}

private void RaiseRandGeneratedEvent(int rand)
{
    var handler = RandGenerated; // 線程安全拷貝
    if (handler != null)
    {
        handler(this, new RandGeneratedEventArgs(rand));
    }
}

關鍵認知:事件本質 = 私有委托字段 + 兩個存取器

Reflector 反編譯顯示:編譯器會生成 private EventHandler<T> RandGenerated; 字段,add_RandGenerated/remove_RandGenerated 分別調用 Delegate.Combine/Remove 修改該字段。

Emit 逐步實現

1. 定義委托類型與後備字段

var eventType = typeof(EventHandler<RandGeneratedEventArgs>);
var backingField = typeBuilder.DefineField(
    "RandGenerated", eventType, FieldAttributes.Private);

2. 建立 EventBuilder

var evtBuilder = typeBuilder.DefineEvent(
    "RandGenerated", EventAttributes.None, eventType);

3. 實現 add_ 存取器

var addMethod = typeBuilder.DefineMethod(
    "add_RandGenerated",
    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual,
    null,
    new[] { eventType });

var ilAdd = addMethod.GetILGenerator();
// this.RandGenerated = Delegate.Combine(this.RandGenerated, value)
ilAdd.Emit(OpCodes.Ldarg_0);                    // this
ilAdd.Emit(OpCodes.Ldarg_0);                    // this
ilAdd.Emit(OpCodes.Ldfld, backingField);        // this.RandGenerated
ilAdd.Emit(OpCodes.Ldarg_1);                    // value (新委托)
ilAdd.Emit(OpCodes.Call, typeof(Delegate).GetMethod("Combine", new[] { eventType, eventType }));
ilAdd.Emit(OpCodes.Castclass, eventType);       // 轉換回強類型
ilAdd.Emit(OpCodes.Stfld, backingField);        // 存回字段
ilAdd.Emit(OpCodes.Ret);

evtBuilder.SetAddOnMethod(addMethod);

4. 實現 remove_ 存取器

var removeMethod = typeBuilder.DefineMethod(
    "remove_RandGenerated",
    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual,
    null,
    new[] { eventType });

var ilRemove = removeMethod.GetILGenerator();
// this.RandGenerated = Delegate.Remove(this.RandGenerated, value)
ilRemove.Emit(OpCodes.Ldarg_0);
ilRemove.Emit(OpCodes.Ldarg_0);
ilRemove.Emit(OpCodes.Ldfld, backingField);
ilRemove.Emit(OpCodes.Ldarg_1);
ilRemove.Emit(OpCodes.Call, typeof(Delegate).GetMethod("Remove", new[] { eventType, eventType }));
ilRemove.Emit(OpCodes.Castclass, eventType);
ilRemove.Emit(OpCodes.Stfld, backingField);
ilRemove.Emit(OpCodes.Ret);

evtBuilder.SetRemoveOnMethod(removeMethod);

5. 觸發事件的輔助方法 (RaiseEvent)

var raiseMethod = typeBuilder.DefineMethod(
    "RaiseRandGeneratedEvent",
    MethodAttributes.Private | MethodAttributes.HideBySig,
    null,
    new[] { typeof(int) });

var ilRaise = raiseMethod.GetILGenerator();
var labelIfNull = ilRaise.DefineLabel();
var labelEnd = ilRaise.DefineLabel();

// var temp = this.RandGenerated
ilRaise.Emit(OpCodes.Ldarg_0);
ilRaise.Emit(OpCodes.Ldfld, backingField);
ilRaise.Emit(OpCodes.Dup); // 複製一份用於後續判空
ilRaise.Emit(OpCodes.Brtrue_S, labelIfNull);
ilRaise.Emit(OpCodes.Pop); // 為 null 彈出
ilRaise.Emit(OpCodes.Br_S, labelEnd);

ilRaise.MarkLabel(labelIfNull);
// temp(this, new RandGeneratedEventArgs(rand))
ilRaise.Emit(OpCodes.Ldarg_0); // this
ilRaise.Emit(OpCodes.Ldarg_1); // rand
ilRaise.Emit(OpCodes.Newobj, typeof(RandGeneratedEventArgs).GetConstructor(new[] { typeof(int) }));
ilRaise.Emit(OpCodes.Callvirt, eventType.GetMethod("Invoke"));
ilRaise.MarkLabel(labelEnd);
ilRaise.Emit(OpCodes.Ret);

驗證要點

  • 生成組件後用 ildasm 或 Reflector 檢查 .event 元數據是否包含 .addon/.removeon。
  • 編寫測試代碼訂閱事件,確認 +=/-= 編譯為 call add_RandGenerated/call remove_RandGenerated。
  • 多線程場景下,Raise 方法的拷貝模式 (var temp = field) 保證線程安全。

事件發射是 Emit 進階能力的試金石,搞懂委托合併/移除與字段交互,才能構建完整的動態領域模型。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved