[{"data":1,"prerenderedAt":1521},["ShallowReactive",2],{"article-\u002Farticles\u002Fdotnet-reflection-generic-handler-registration":3},{"id":4,"title":5,"body":6,"date":1507,"description":24,"excerpt":1508,"extension":1509,"meta":1510,"navigation":104,"path":1511,"seo":1512,"stem":1513,"tags":1514,"__hash__":1520},"articles\u002Farticles\u002Fdotnet-reflection-generic-handler-registration.md","用反射自動註冊泛型 Handler：找出所有 IEventHandler\u003CT>，再在執行時依事件型別派送",{"type":7,"value":8,"toc":1497},"minimark",[9,14,18,163,173,228,236,247,255,262,526,529,601,621,628,660,664,667,791,804,987,995,1006,1010,1017,1034,1370,1373,1376,1436,1440,1443,1468,1471,1474,1493],[10,11,13],"h2",{"id":12},"問題手動註冊一定會漏","問題：手動註冊一定會漏",[15,16,17],"p",{},"做事件匯流排時，訂閱者的寫法通常是實作一個泛型介面：",[19,20,25],"pre",{"className":21,"code":22,"language":23,"meta":24,"style":24},"language-csharp shiki shiki-themes github-dark","public interface IEventHandler\u003Cin TEvent>\n{\n    Task HandleAsync(TEvent @event, CancellationToken cancellationToken);\n}\n\npublic class OrderCreatedHandler : IEventHandler\u003COrderCreated> { \u002F* ... *\u002F }\npublic class SendWelcomeMailHandler : IEventHandler\u003CMemberRegistered> { \u002F* ... *\u002F }\n","csharp","",[26,27,28,57,63,93,99,106,138],"code",{"__ignoreMap":24},[29,30,33,37,40,44,48,51,54],"span",{"class":31,"line":32},"line",1,[29,34,36],{"class":35},"snl16","public",[29,38,39],{"class":35}," interface",[29,41,43],{"class":42},"svObZ"," IEventHandler",[29,45,47],{"class":46},"s95oV","\u003C",[29,49,50],{"class":35},"in",[29,52,53],{"class":42}," TEvent",[29,55,56],{"class":46},">\n",[29,58,60],{"class":31,"line":59},2,[29,61,62],{"class":46},"{\n",[29,64,66,69,72,75,78,81,84,87,90],{"class":31,"line":65},3,[29,67,68],{"class":42},"    Task",[29,70,71],{"class":42}," HandleAsync",[29,73,74],{"class":46},"(",[29,76,77],{"class":42},"TEvent",[29,79,80],{"class":42}," @event",[29,82,83],{"class":46},", ",[29,85,86],{"class":42},"CancellationToken",[29,88,89],{"class":42}," cancellationToken",[29,91,92],{"class":46},");\n",[29,94,96],{"class":31,"line":95},4,[29,97,98],{"class":46},"}\n",[29,100,102],{"class":31,"line":101},5,[29,103,105],{"emptyLinePlaceholder":104},true,"\n",[29,107,109,111,114,117,120,123,125,128,131,135],{"class":31,"line":108},6,[29,110,36],{"class":35},[29,112,113],{"class":35}," class",[29,115,116],{"class":42}," OrderCreatedHandler",[29,118,119],{"class":46}," : ",[29,121,122],{"class":42},"IEventHandler",[29,124,47],{"class":46},[29,126,127],{"class":42},"OrderCreated",[29,129,130],{"class":46},"> { ",[29,132,134],{"class":133},"sAwPA","\u002F* ... *\u002F",[29,136,137],{"class":46}," }\n",[29,139,141,143,145,148,150,152,154,157,159,161],{"class":31,"line":140},7,[29,142,36],{"class":35},[29,144,113],{"class":35},[29,146,147],{"class":42}," SendWelcomeMailHandler",[29,149,119],{"class":46},[29,151,122],{"class":42},[29,153,47],{"class":46},[29,155,156],{"class":42},"MemberRegistered",[29,158,130],{"class":46},[29,160,134],{"class":133},[29,162,137],{"class":46},[15,164,165,166,168,169,172],{},"接著要讓框架知道「",[26,167,127],{}," 事件要交給 ",[26,170,171],{},"OrderCreatedHandler","」，最直接的方式是手動註冊：",[19,174,176],{"className":21,"code":175,"language":23,"meta":24,"style":24},"services.AddScoped\u003CIEventHandler\u003COrderCreated>, OrderCreatedHandler>();\nservices.AddScoped\u003CIEventHandler\u003CMemberRegistered>, SendWelcomeMailHandler>();\n\u002F\u002F ……第 30 個 Handler\n",[26,177,178,202,223],{"__ignoreMap":24},[29,179,180,183,186,188,190,192,194,197,199],{"class":31,"line":32},[29,181,182],{"class":46},"services.",[29,184,185],{"class":42},"AddScoped",[29,187,47],{"class":46},[29,189,122],{"class":42},[29,191,47],{"class":46},[29,193,127],{"class":42},[29,195,196],{"class":46},">, ",[29,198,171],{"class":42},[29,200,201],{"class":46},">();\n",[29,203,204,206,208,210,212,214,216,218,221],{"class":31,"line":59},[29,205,182],{"class":46},[29,207,185],{"class":42},[29,209,47],{"class":46},[29,211,122],{"class":42},[29,213,47],{"class":46},[29,215,156],{"class":42},[29,217,196],{"class":46},[29,219,220],{"class":42},"SendWelcomeMailHandler",[29,222,201],{"class":46},[29,224,225],{"class":31,"line":65},[29,226,227],{"class":133},"\u002F\u002F ……第 30 個 Handler\n",[15,229,230,231,235],{},"Handler 一多，就一定會有人寫好了新的 Handler、卻忘了註冊。更麻煩的是，",[232,233,234],"strong",{},"忘記註冊不會有任何錯誤","：事件照常發布，只是沒有人處理，要到資料對不起來時才會發現。",[15,237,238,239,246],{},"比較好的做法是：",[232,240,241,242,245],{},"只要實作了 ",[26,243,244],{},"IEventHandler\u003CT>","，就自動被註冊","。這需要用到反射。",[10,248,250,251],{"id":249},"掃描找出所有-ieventhandler-的實作","掃描：找出所有 IEventHandler",[252,253,254],"t",{}," 的實作",[15,256,257],{},[258,259],"img",{"alt":260,"src":261},"反射註冊流程示意圖：① 以 assembly.GetTypes 列出組件中的型別，只留下非抽象的類別，排除不是 Handler 的類別、抽象類別與介面本身；② 以 GetInterfaces 找出 IsGenericType 且泛型定義等於 IEventHandler 的介面，NotifyHandler 同時實作了兩個；③ 以 GetGenericArguments 取出事件型別；④ 登記事件型別對應 Handler 的對照表，並註冊進 DI。反射只在應用程式啟動時跑一次，之後發布事件時是查表加上從 DI 取出 Handler","\u002Fimages\u002Farticles\u002Freflection-handler-registration.svg",[19,263,265],{"className":21,"code":264,"language":23,"meta":24,"style":24},"public static IServiceCollection AddEventHandlersFromAssembly(\n    this IServiceCollection services, Assembly assembly)\n{\n    var registrations =\n        from type in assembly.GetTypes()\n        where type is { IsClass: true, IsAbstract: false, IsGenericTypeDefinition: false }\n        from @interface in type.GetInterfaces()\n        where @interface.IsGenericType\n           && @interface.GetGenericTypeDefinition() == typeof(IEventHandler\u003C>)\n        select (ServiceType: @interface, ImplementationType: type);\n\n    foreach (var (serviceType, implementationType) in registrations)\n    {\n        \u002F\u002F 例如：IEventHandler\u003COrderCreated> → OrderCreatedHandler\n        services.AddScoped(serviceType, implementationType);\n    }\n\n    return services;\n}\n",[26,266,267,283,304,308,319,339,370,389,395,423,444,449,478,484,490,501,507,512,521],{"__ignoreMap":24},[29,268,269,271,274,277,280],{"class":31,"line":32},[29,270,36],{"class":35},[29,272,273],{"class":35}," static",[29,275,276],{"class":42}," IServiceCollection",[29,278,279],{"class":42}," AddEventHandlersFromAssembly",[29,281,282],{"class":46},"(\n",[29,284,285,288,290,293,295,298,301],{"class":31,"line":59},[29,286,287],{"class":35},"    this",[29,289,276],{"class":42},[29,291,292],{"class":42}," services",[29,294,83],{"class":46},[29,296,297],{"class":42},"Assembly",[29,299,300],{"class":42}," assembly",[29,302,303],{"class":46},")\n",[29,305,306],{"class":31,"line":65},[29,307,62],{"class":46},[29,309,310,313,316],{"class":31,"line":95},[29,311,312],{"class":35},"    var",[29,314,315],{"class":42}," registrations",[29,317,318],{"class":35}," =\n",[29,320,321,324,327,330,333,336],{"class":31,"line":101},[29,322,323],{"class":35},"        from",[29,325,326],{"class":42}," type",[29,328,329],{"class":35}," in",[29,331,332],{"class":46}," assembly.",[29,334,335],{"class":42},"GetTypes",[29,337,338],{"class":46},"()\n",[29,340,341,344,347,350,353,357,360,363,366,368],{"class":31,"line":108},[29,342,343],{"class":35},"        where",[29,345,346],{"class":46}," type ",[29,348,349],{"class":35},"is",[29,351,352],{"class":46}," { IsClass: ",[29,354,356],{"class":355},"sDLfK","true",[29,358,359],{"class":46},", IsAbstract: ",[29,361,362],{"class":355},"false",[29,364,365],{"class":46},", IsGenericTypeDefinition: ",[29,367,362],{"class":355},[29,369,137],{"class":46},[29,371,372,374,377,379,381,384,387],{"class":31,"line":140},[29,373,323],{"class":42},[29,375,376],{"class":42}," @interface",[29,378,329],{"class":42},[29,380,326],{"class":42},[29,382,383],{"class":46},".",[29,385,386],{"class":42},"GetInterfaces",[29,388,338],{"class":46},[29,390,392],{"class":31,"line":391},8,[29,393,394],{"class":46},"        where @interface.IsGenericType\n",[29,396,398,401,404,407,410,413,416,418,420],{"class":31,"line":397},9,[29,399,400],{"class":35},"           &&",[29,402,403],{"class":46}," @interface.",[29,405,406],{"class":42},"GetGenericTypeDefinition",[29,408,409],{"class":46},"() ",[29,411,412],{"class":35},"==",[29,414,415],{"class":35}," typeof",[29,417,74],{"class":46},[29,419,122],{"class":42},[29,421,422],{"class":46},"\u003C>)\n",[29,424,426,429,432,435,438,441],{"class":31,"line":425},10,[29,427,428],{"class":42},"        select",[29,430,431],{"class":46}," (",[29,433,434],{"class":42},"ServiceType",[29,436,437],{"class":46},": @interface, ",[29,439,440],{"class":42},"ImplementationType",[29,442,443],{"class":46},": type);\n",[29,445,447],{"class":31,"line":446},11,[29,448,105],{"emptyLinePlaceholder":104},[29,450,452,455,457,460,462,465,467,470,473,475],{"class":31,"line":451},12,[29,453,454],{"class":35},"    foreach",[29,456,431],{"class":46},[29,458,459],{"class":35},"var",[29,461,431],{"class":46},[29,463,464],{"class":42},"serviceType",[29,466,83],{"class":46},[29,468,469],{"class":42},"implementationType",[29,471,472],{"class":46},") ",[29,474,50],{"class":35},[29,476,477],{"class":46}," registrations)\n",[29,479,481],{"class":31,"line":480},13,[29,482,483],{"class":46},"    {\n",[29,485,487],{"class":31,"line":486},14,[29,488,489],{"class":133},"        \u002F\u002F 例如：IEventHandler\u003COrderCreated> → OrderCreatedHandler\n",[29,491,493,496,498],{"class":31,"line":492},15,[29,494,495],{"class":46},"        services.",[29,497,185],{"class":42},[29,499,500],{"class":46},"(serviceType, implementationType);\n",[29,502,504],{"class":31,"line":503},16,[29,505,506],{"class":46},"    }\n",[29,508,510],{"class":31,"line":509},17,[29,511,105],{"emptyLinePlaceholder":104},[29,513,515,518],{"class":31,"line":514},18,[29,516,517],{"class":35},"    return",[29,519,520],{"class":46}," services;\n",[29,522,524],{"class":31,"line":523},19,[29,525,98],{"class":46},[15,527,528],{},"逐步拆解：",[530,531,532,541,563,575,595],"ol",{},[533,534,535,540],"li",{},[232,536,537],{},[26,538,539],{},"assembly.GetTypes()","：列出組件中的所有型別",[533,542,543,546,547,550,551,554,555,558,559,562],{},[232,544,545],{},"篩選可以被建立的類別","：",[26,548,549],{},"IsClass"," 排除介面與結構，",[26,552,553],{},"IsAbstract"," 排除抽象類別，",[26,556,557],{},"IsGenericTypeDefinition"," 排除還沒指定型別參數的開放泛型類別（例如 ",[26,560,561],{},"LoggingHandler\u003CT>","，它沒辦法直接被建立）",[533,564,565,570,571,574],{},[232,566,567],{},[26,568,569],{},"GetInterfaces()","：取得這個類別實作的",[232,572,573],{},"所有","介面，包含從父類別繼承來的",[533,576,577,546,580,583,584,587,588,591,592],{},[232,578,579],{},"比對泛型定義",[26,581,582],{},"IEventHandler\u003COrderCreated>"," 是一個「封閉泛型型別」，它的 ",[26,585,586],{},"GetGenericTypeDefinition()"," 是「開放泛型型別」",[26,589,590],{},"IEventHandler\u003C>","。用開放泛型比對，才能找出所有的 ",[26,593,594],{},"IEventHandler\u003C任何型別>",[533,596,597,600],{},[232,598,599],{},"註冊","：以封閉泛型介面為服務型別、Handler 類別為實作型別註冊進 DI",[15,602,603,604,606,607,609,610,613,614,617,618,620],{},"一個類別實作了好幾個 ",[26,605,244],{},"（例如同時處理 ",[26,608,127],{}," 和 ",[26,611,612],{},"PaymentPaid"," 的 ",[26,615,616],{},"NotifyHandler","），",[26,619,569],{}," 會回傳兩個介面，兩個都會被註冊。",[15,622,623,624,627],{},"如果需要事件型別本身（例如要建立「事件型別 → Handler 型別」的對照表，或是要用事件型別名稱訂閱 RabbitMQ 的 Routing Key），可以用 ",[26,625,626],{},"GetGenericArguments()[0]"," 取出：",[19,629,631],{"className":21,"code":630,"language":23,"meta":24,"style":24},"var eventType = @interface.GetGenericArguments()[0];   \u002F\u002F typeof(OrderCreated)\n",[26,632,633],{"__ignoreMap":24},[29,634,635,637,640,643,645,648,651,654,657],{"class":31,"line":32},[29,636,459],{"class":35},[29,638,639],{"class":42}," eventType",[29,641,642],{"class":35}," =",[29,644,403],{"class":46},[29,646,647],{"class":42},"GetGenericArguments",[29,649,650],{"class":46},"()[",[29,652,653],{"class":355},"0",[29,655,656],{"class":46},"];   ",[29,658,659],{"class":133},"\u002F\u002F typeof(OrderCreated)\n",[10,661,663],{"id":662},"派送執行時只知道-object怎麼找到-handler","派送：執行時只知道 object，怎麼找到 Handler？",[15,665,666],{},"註冊好了，接下來是發布事件。如果發布端在編譯時就知道事件型別，直接用泛型即可：",[19,668,670],{"className":21,"code":669,"language":23,"meta":24,"style":24},"public async Task PublishAsync\u003CTEvent>(TEvent @event, CancellationToken ct)\n{\n    using var scope = _scopeFactory.CreateScope();\n    var handlers = scope.ServiceProvider.GetServices\u003CIEventHandler\u003CTEvent>>();\n    foreach (var handler in handlers)\n        await handler.HandleAsync(@event, ct);\n}\n",[26,671,672,705,709,731,757,773,787],{"__ignoreMap":24},[29,673,674,676,679,682,685,687,689,692,694,696,698,700,703],{"class":31,"line":32},[29,675,36],{"class":35},[29,677,678],{"class":35}," async",[29,680,681],{"class":42}," Task",[29,683,684],{"class":42}," PublishAsync",[29,686,47],{"class":46},[29,688,77],{"class":42},[29,690,691],{"class":46},">(",[29,693,77],{"class":42},[29,695,80],{"class":42},[29,697,83],{"class":46},[29,699,86],{"class":42},[29,701,702],{"class":42}," ct",[29,704,303],{"class":46},[29,706,707],{"class":31,"line":59},[29,708,62],{"class":46},[29,710,711,714,717,720,722,725,728],{"class":31,"line":65},[29,712,713],{"class":35},"    using",[29,715,716],{"class":35}," var",[29,718,719],{"class":42}," scope",[29,721,642],{"class":35},[29,723,724],{"class":46}," _scopeFactory.",[29,726,727],{"class":42},"CreateScope",[29,729,730],{"class":46},"();\n",[29,732,733,735,738,740,743,746,748,750,752,754],{"class":31,"line":95},[29,734,312],{"class":35},[29,736,737],{"class":42}," handlers",[29,739,642],{"class":35},[29,741,742],{"class":46}," scope.ServiceProvider.",[29,744,745],{"class":42},"GetServices",[29,747,47],{"class":46},[29,749,122],{"class":42},[29,751,47],{"class":46},[29,753,77],{"class":42},[29,755,756],{"class":46},">>();\n",[29,758,759,761,763,765,768,770],{"class":31,"line":101},[29,760,454],{"class":35},[29,762,431],{"class":46},[29,764,459],{"class":35},[29,766,767],{"class":42}," handler",[29,769,329],{"class":35},[29,771,772],{"class":46}," handlers)\n",[29,774,775,778,781,784],{"class":31,"line":108},[29,776,777],{"class":35},"        await",[29,779,780],{"class":46}," handler.",[29,782,783],{"class":42},"HandleAsync",[29,785,786],{"class":46},"(@event, ct);\n",[29,788,789],{"class":31,"line":140},[29,790,98],{"class":46},[15,792,793,794,797,798,800,801,546],{},"但從訊息佇列收到的訊息，反序列化之後只是一個 ",[26,795,796],{},"object","（事件型別是從訊息標頭裡的型別名稱解析出來的），編譯時並不知道是 ",[26,799,127],{},"。這時就要在",[232,802,803],{},"執行時組出泛型型別",[19,805,807],{"className":21,"code":806,"language":23,"meta":24,"style":24},"public async Task DispatchAsync(object @event, CancellationToken ct)\n{\n    var handlerType = typeof(IEventHandler\u003C>).MakeGenericType(@event.GetType());\n    \u002F\u002F                 IEventHandler\u003C>  +  OrderCreated  →  IEventHandler\u003COrderCreated>\n\n    using var scope = _scopeFactory.CreateScope();\n    foreach (var handler in scope.ServiceProvider.GetServices(handlerType))\n    {\n        var method = handlerType.GetMethod(nameof(IEventHandler\u003Cobject>.HandleAsync))!;\n        await (Task)method.Invoke(handler, new[] { @event, ct })!;\n    }\n}\n",[26,808,809,834,838,868,873,877,893,912,916,951,979,983],{"__ignoreMap":24},[29,810,811,813,815,817,820,822,824,826,828,830,832],{"class":31,"line":32},[29,812,36],{"class":35},[29,814,678],{"class":35},[29,816,681],{"class":42},[29,818,819],{"class":42}," DispatchAsync",[29,821,74],{"class":46},[29,823,796],{"class":35},[29,825,80],{"class":42},[29,827,83],{"class":46},[29,829,86],{"class":42},[29,831,702],{"class":42},[29,833,303],{"class":46},[29,835,836],{"class":31,"line":59},[29,837,62],{"class":46},[29,839,840,842,845,847,849,851,853,856,859,862,865],{"class":31,"line":65},[29,841,312],{"class":35},[29,843,844],{"class":42}," handlerType",[29,846,642],{"class":35},[29,848,415],{"class":35},[29,850,74],{"class":46},[29,852,122],{"class":42},[29,854,855],{"class":46},"\u003C>).",[29,857,858],{"class":42},"MakeGenericType",[29,860,861],{"class":46},"(@event.",[29,863,864],{"class":42},"GetType",[29,866,867],{"class":46},"());\n",[29,869,870],{"class":31,"line":95},[29,871,872],{"class":133},"    \u002F\u002F                 IEventHandler\u003C>  +  OrderCreated  →  IEventHandler\u003COrderCreated>\n",[29,874,875],{"class":31,"line":101},[29,876,105],{"emptyLinePlaceholder":104},[29,878,879,881,883,885,887,889,891],{"class":31,"line":108},[29,880,713],{"class":35},[29,882,716],{"class":35},[29,884,719],{"class":42},[29,886,642],{"class":35},[29,888,724],{"class":46},[29,890,727],{"class":42},[29,892,730],{"class":46},[29,894,895,897,899,901,903,905,907,909],{"class":31,"line":140},[29,896,454],{"class":35},[29,898,431],{"class":46},[29,900,459],{"class":35},[29,902,767],{"class":42},[29,904,329],{"class":35},[29,906,742],{"class":46},[29,908,745],{"class":42},[29,910,911],{"class":46},"(handlerType))\n",[29,913,914],{"class":31,"line":391},[29,915,483],{"class":46},[29,917,918,921,924,926,929,932,934,937,940,942,945,948],{"class":31,"line":397},[29,919,920],{"class":35},"        var",[29,922,923],{"class":42}," method",[29,925,642],{"class":35},[29,927,928],{"class":46}," handlerType.",[29,930,931],{"class":42},"GetMethod",[29,933,74],{"class":46},[29,935,936],{"class":35},"nameof",[29,938,939],{"class":46},"(IEventHandler\u003C",[29,941,796],{"class":35},[29,943,944],{"class":46},">.HandleAsync))",[29,946,947],{"class":35},"!",[29,949,950],{"class":46},";\n",[29,952,953,955,957,960,963,966,969,972,975,977],{"class":31,"line":425},[29,954,777],{"class":35},[29,956,431],{"class":46},[29,958,959],{"class":42},"Task",[29,961,962],{"class":46},")method.",[29,964,965],{"class":42},"Invoke",[29,967,968],{"class":46},"(handler, ",[29,970,971],{"class":35},"new",[29,973,974],{"class":46},"[] { @event, ct })",[29,976,947],{"class":35},[29,978,950],{"class":46},[29,980,981],{"class":31,"line":446},[29,982,506],{"class":46},[29,984,985],{"class":31,"line":451},[29,986,98],{"class":46},[15,988,989,991,992,994],{},[26,990,858],{}," 是 ",[26,993,406],{}," 的反向操作：把開放泛型填上型別參數，變成封閉泛型。",[15,996,997,998,1001,1002,1005],{},"每一個事件都",[232,999,1000],{},"建立一個新的 DI Scope","，Handler 如果注入了 ",[26,1003,1004],{},"DbContext"," 這類 Scoped 服務，每個事件都會拿到獨立的實例，不會互相干擾。",[10,1007,1009],{"id":1008},"效能反射放在啟動時執行時快取","效能：反射放在啟動時，執行時快取",[15,1011,1012,1013,1016],{},"反射的成本主要在「找」：列舉組件中的所有型別、取得介面、查詢方法。這些動作放在",[232,1014,1015],{},"應用程式啟動時跑一次","完全沒問題，但不應該在每次發布事件時重做。",[15,1018,1019,1020,1023,1024,1026,1027,1029,1030,1033],{},"上面的 ",[26,1021,1022],{},"DispatchAsync"," 每次都呼叫 ",[26,1025,858],{}," 與 ",[26,1028,931],{},"，事件量大時可以依事件型別快取起來，並把 ",[26,1031,1032],{},"MethodInfo.Invoke"," 換成編譯好的委派：",[19,1035,1037],{"className":21,"code":1036,"language":23,"meta":24,"style":24},"private static readonly ConcurrentDictionary\u003CType, Func\u003Cobject, object, CancellationToken, Task>> Invokers = new();\n\nprivate static Func\u003Cobject, object, CancellationToken, Task> GetInvoker(Type eventType) =>\n    Invokers.GetOrAdd(eventType, static t =>\n    {\n        var handlerType = typeof(IEventHandler\u003C>).MakeGenericType(t);\n        var method = handlerType.GetMethod(\"HandleAsync\")!;\n\n        var handler = Expression.Parameter(typeof(object));\n        var @event  = Expression.Parameter(typeof(object));\n        var ct      = Expression.Parameter(typeof(CancellationToken));\n\n        var call = Expression.Call(\n            Expression.Convert(handler, handlerType), method,\n            Expression.Convert(@event, t), ct);\n\n        return Expression.Lambda\u003CFunc\u003Cobject, object, CancellationToken, Task>>(call, handler, @event, ct).Compile();\n    });\n",[26,1038,1039,1091,1095,1137,1157,1161,1182,1207,1211,1237,1260,1283,1287,1303,1314,1323,1327,1365],{"__ignoreMap":24},[29,1040,1041,1044,1046,1049,1052,1054,1057,1059,1062,1064,1066,1068,1070,1072,1074,1076,1078,1081,1084,1086,1089],{"class":31,"line":32},[29,1042,1043],{"class":35},"private",[29,1045,273],{"class":35},[29,1047,1048],{"class":35}," readonly",[29,1050,1051],{"class":42}," ConcurrentDictionary",[29,1053,47],{"class":46},[29,1055,1056],{"class":42},"Type",[29,1058,83],{"class":46},[29,1060,1061],{"class":42},"Func",[29,1063,47],{"class":46},[29,1065,796],{"class":35},[29,1067,83],{"class":46},[29,1069,796],{"class":35},[29,1071,83],{"class":46},[29,1073,86],{"class":42},[29,1075,83],{"class":46},[29,1077,959],{"class":42},[29,1079,1080],{"class":46},">> ",[29,1082,1083],{"class":42},"Invokers",[29,1085,642],{"class":35},[29,1087,1088],{"class":35}," new",[29,1090,730],{"class":46},[29,1092,1093],{"class":31,"line":59},[29,1094,105],{"emptyLinePlaceholder":104},[29,1096,1097,1099,1101,1104,1106,1108,1110,1112,1114,1116,1118,1120,1123,1126,1128,1130,1132,1134],{"class":31,"line":65},[29,1098,1043],{"class":35},[29,1100,273],{"class":35},[29,1102,1103],{"class":42}," Func",[29,1105,47],{"class":46},[29,1107,796],{"class":35},[29,1109,83],{"class":46},[29,1111,796],{"class":35},[29,1113,83],{"class":46},[29,1115,86],{"class":42},[29,1117,83],{"class":46},[29,1119,959],{"class":42},[29,1121,1122],{"class":46},"> ",[29,1124,1125],{"class":42},"GetInvoker",[29,1127,74],{"class":46},[29,1129,1056],{"class":42},[29,1131,639],{"class":42},[29,1133,472],{"class":46},[29,1135,1136],{"class":35},"=>\n",[29,1138,1139,1142,1145,1148,1151,1154],{"class":31,"line":95},[29,1140,1141],{"class":46},"    Invokers.",[29,1143,1144],{"class":42},"GetOrAdd",[29,1146,1147],{"class":46},"(eventType, ",[29,1149,1150],{"class":35},"static",[29,1152,1153],{"class":42}," t",[29,1155,1156],{"class":35}," =>\n",[29,1158,1159],{"class":31,"line":101},[29,1160,483],{"class":46},[29,1162,1163,1165,1167,1169,1171,1173,1175,1177,1179],{"class":31,"line":108},[29,1164,920],{"class":35},[29,1166,844],{"class":42},[29,1168,642],{"class":35},[29,1170,415],{"class":35},[29,1172,74],{"class":46},[29,1174,122],{"class":42},[29,1176,855],{"class":46},[29,1178,858],{"class":42},[29,1180,1181],{"class":46},"(t);\n",[29,1183,1184,1186,1188,1190,1192,1194,1196,1200,1203,1205],{"class":31,"line":140},[29,1185,920],{"class":35},[29,1187,923],{"class":42},[29,1189,642],{"class":35},[29,1191,928],{"class":46},[29,1193,931],{"class":42},[29,1195,74],{"class":46},[29,1197,1199],{"class":1198},"sU2Wk","\"HandleAsync\"",[29,1201,1202],{"class":46},")",[29,1204,947],{"class":35},[29,1206,950],{"class":46},[29,1208,1209],{"class":31,"line":391},[29,1210,105],{"emptyLinePlaceholder":104},[29,1212,1213,1215,1217,1219,1222,1225,1227,1230,1232,1234],{"class":31,"line":397},[29,1214,920],{"class":35},[29,1216,767],{"class":42},[29,1218,642],{"class":35},[29,1220,1221],{"class":46}," Expression.",[29,1223,1224],{"class":42},"Parameter",[29,1226,74],{"class":46},[29,1228,1229],{"class":35},"typeof",[29,1231,74],{"class":46},[29,1233,796],{"class":35},[29,1235,1236],{"class":46},"));\n",[29,1238,1239,1241,1243,1246,1248,1250,1252,1254,1256,1258],{"class":31,"line":425},[29,1240,920],{"class":35},[29,1242,80],{"class":42},[29,1244,1245],{"class":35},"  =",[29,1247,1221],{"class":46},[29,1249,1224],{"class":42},[29,1251,74],{"class":46},[29,1253,1229],{"class":35},[29,1255,74],{"class":46},[29,1257,796],{"class":35},[29,1259,1236],{"class":46},[29,1261,1262,1264,1266,1269,1271,1273,1275,1277,1279,1281],{"class":31,"line":446},[29,1263,920],{"class":35},[29,1265,702],{"class":42},[29,1267,1268],{"class":35},"      =",[29,1270,1221],{"class":46},[29,1272,1224],{"class":42},[29,1274,74],{"class":46},[29,1276,1229],{"class":35},[29,1278,74],{"class":46},[29,1280,86],{"class":42},[29,1282,1236],{"class":46},[29,1284,1285],{"class":31,"line":451},[29,1286,105],{"emptyLinePlaceholder":104},[29,1288,1289,1291,1294,1296,1298,1301],{"class":31,"line":480},[29,1290,920],{"class":35},[29,1292,1293],{"class":42}," call",[29,1295,642],{"class":35},[29,1297,1221],{"class":46},[29,1299,1300],{"class":42},"Call",[29,1302,282],{"class":46},[29,1304,1305,1308,1311],{"class":31,"line":486},[29,1306,1307],{"class":46},"            Expression.",[29,1309,1310],{"class":42},"Convert",[29,1312,1313],{"class":46},"(handler, handlerType), method,\n",[29,1315,1316,1318,1320],{"class":31,"line":492},[29,1317,1307],{"class":46},[29,1319,1310],{"class":42},[29,1321,1322],{"class":46},"(@event, t), ct);\n",[29,1324,1325],{"class":31,"line":503},[29,1326,105],{"emptyLinePlaceholder":104},[29,1328,1329,1332,1334,1337,1339,1341,1343,1345,1347,1349,1351,1353,1355,1357,1360,1363],{"class":31,"line":509},[29,1330,1331],{"class":35},"        return",[29,1333,1221],{"class":46},[29,1335,1336],{"class":42},"Lambda",[29,1338,47],{"class":46},[29,1340,1061],{"class":42},[29,1342,47],{"class":46},[29,1344,796],{"class":35},[29,1346,83],{"class":46},[29,1348,796],{"class":35},[29,1350,83],{"class":46},[29,1352,86],{"class":42},[29,1354,83],{"class":46},[29,1356,959],{"class":42},[29,1358,1359],{"class":46},">>(call, handler, @event, ct).",[29,1361,1362],{"class":42},"Compile",[29,1364,730],{"class":46},[29,1366,1367],{"class":31,"line":514},[29,1368,1369],{"class":46},"    });\n",[15,1371,1372],{},"每個事件型別只會建立一次委派，之後的呼叫和一般的方法呼叫速度相當。",[10,1374,1375],{"id":1375},"實務上要注意的地方",[1377,1378,1379,1395,1405,1422],"ul",{},[533,1380,1381,546,1384,1387,1388,1391,1392],{},[232,1382,1383],{},"掃描哪些組件",[26,1385,1386],{},"Assembly.GetExecutingAssembly()"," 指的是「目前執行中的程式碼所在的組件」——如果掃描的程式寫在函式庫裡，拿到的是函式庫本身，而不是使用者的應用程式。比較明確的做法是讓呼叫端傳入，例如 ",[26,1389,1390],{},"AddEventHandlersFromAssembly(typeof(Program).Assembly)"," 或 ",[26,1393,1394],{},"Assembly.GetEntryAssembly()",[533,1396,1397,1400,1401,1404],{},[232,1398,1399],{},"重複註冊","：同一個組件被掃描兩次，Handler 會被註冊兩次、事件被處理兩次。可以用 ",[26,1402,1403],{},"TryAddEnumerable"," 註冊，相同的服務與實作組合只會加入一次",[533,1406,1407,1413,1414,1417,1418,1421],{},[232,1408,1409,1412],{},[26,1410,1411],{},"GetTypes()"," 可能拋出例外","：組件中有型別的相依組件載入失敗時，會拋出 ",[26,1415,1416],{},"ReflectionTypeLoadException","，可以從例外的 ",[26,1419,1420],{},"Types"," 屬性取出成功載入的部分",[533,1423,1424,1427,1428,1435],{},[232,1425,1426],{},"現成的工具","：只是要「掃描並註冊」，",[1429,1430,1434],"a",{"href":1431,"rel":1432},"https:\u002F\u002Fgithub.com\u002Fkhellang\u002FScrutor",[1433],"nofollow","Scrutor"," 這類套件已經做得很完整；在意啟動時間或要做 AOT 編譯的話，可以考慮用 Source Generator 在編譯時產生註冊程式碼，完全避開執行時的反射",[10,1437,1439],{"id":1438},"使用情境自製的-eventbus-函式庫","使用情境：自製的 EventBus 函式庫",[15,1441,1442],{},"自製的 .NET EventBus 函式庫就是用這套方式處理訂閱：",[1377,1444,1445,1456,1465],{},[533,1446,1447,1448,1451,1452,1455],{},"比照 ",[26,1449,1450],{},"IServiceCollection","，另外設計一個 ",[26,1453,1454],{},"SubscriptionCollection","，記錄「事件型別 → Handler 型別」的對照",[533,1457,1458,1461,1462,1464],{},[26,1459,1460],{},"AddEventHandlersFromAssembly()"," 在啟動時以反射掃描組件，自動登記所有 ",[26,1463,244],{},"，同時把 Handler 註冊進 DI",[533,1466,1467],{},"從記憶體佇列或 RabbitMQ 收到事件時，依事件型別查表，建立 DI Scope 取出 Handler 並呼叫",[15,1469,1470],{},"使用者新增事件處理器時，只需要實作介面，不需要再修改任何註冊程式碼。",[10,1472,1473],{"id":1473},"一句話總結",[15,1475,1476,1477,1479,1480,1482,1483,1486,1487,1489,1490,1492],{},"用 ",[26,1478,1411],{}," 加 ",[26,1481,569],{},"，比對 ",[26,1484,1485],{},"GetGenericTypeDefinition() == typeof(IEventHandler\u003C>)","，就能在啟動時自動找出並註冊所有 Handler；執行時只知道 ",[26,1488,796],{}," 的情況，用 ",[26,1491,858],{}," 組出封閉泛型再從 DI 取出 Handler，並把反射的結果依型別快取成委派，讓反射只發生一次。",[1494,1495,1496],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":24,"searchDepth":59,"depth":59,"links":1498},[1499,1500,1502,1503,1504,1505,1506],{"id":12,"depth":59,"text":13},{"id":249,"depth":59,"text":1501},"掃描：找出所有 IEventHandler 的實作",{"id":662,"depth":59,"text":663},{"id":1008,"depth":59,"text":1009},{"id":1375,"depth":59,"text":1375},{"id":1438,"depth":59,"text":1439},{"id":1473,"depth":59,"text":1473},"2024-02-27",null,"md",{},"\u002Farticles\u002Fdotnet-reflection-generic-handler-registration",{"title":5,"description":24},"articles\u002Fdotnet-reflection-generic-handler-registration",[1515,1516,1517,1518,1519],".NET","C#","Reflection","Dependency Injection","事件驅動","n9LUr-XoGjYcKsXbb5oNKQc6gqcMtD7sJCbkvPnPdpQ",1792959150428]