結論から書くと、リフレクションでイベントを発生させる汎用的な方法はない。
対象のコードを読んで、適当な方法を探すことになる。
発生させたいイベントにadd/removeアクセサが自動で定義されている場合
発生させたいイベントが
public event EventHandler ValueChanged;
のように、add/removeがコードにない場合は、イベントと同名のprivateフィールドを取得することで、イベントを発生させることができる
public static void RaiseEvent<TEventArgs>(
object target,
string eventName,
TEventArgs eventArg)
{
var type = target.GetType();
var field = type.GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
var handler = (MulticastDelegate)field.GetValue(target);
if (handler is null) return;
foreach (var h in handler.GetInvocationList())
h.Method.Invoke(h.Target, new object[] { target, eventArg });
}
使い方
// AはValueChangedイベントを持ち、add/removeアクセサを定義していないものとする
var a = new A();
a.ValueChanged += (sender, e) => System.Diagnostics.Trace.WriteLine("Raised");
RaiseEvent(a, "ValueChanged", EventArgs.Empty);
発生させたいイベントにadd/removeアクセサが定義されており、内部でEventHandlerListを使用している場合
public event EventHandler Click
{
add
{
Events.AddHandler(EventClick, value);
}
remove
{
...
}
}
のようになっており、Events
がEventHandlerList
型のプロパティである場合。
EventHandlerList
は複数のイベントに対し、デリゲートを管理することができる。
そして、イベントの識別用になんらかのobjectを指定することになっており、ここでは、Clickイベント用識別オブジェクトとして、EventClick
が使用されている。
-
EventHandlerList
型のプロパティの名前 - イベントの識別用のオブジェクト
があれば、イベントを発生させることができる。
WinFormsのControlではこの手が使用できる。
public static void RaiseEvent<TEventHandler, TEventArgs>(
object target,
object eventKeyObject,
string eventHandlerListPropertyName,
TEventArgs eventArgs)
where TEventHalder : Delegate
{
var type = target.GetType();
var property = type.GetProperty(eventHandlerListPropertyName, BindingFlags.Instance | BindingFlags.NonPublic);
var list = property.GetValue(target) as System.ComponentModel.EventHandlerList;
var eventDelegate = list[eventKeyObject] as TEventHalder;
eventDelegate?.DynamicInvoke(new[] { target, eventArgs });
}
public static object GetStaticNonPublicField(
Type target,
string fieldName)
{
return target.GetField(fieldName, BindingFlags.Static | BindingFlags.NonPublic).GetValue(target);
}
使い方
using (var label = new Label())
{
label.Click += (sender, e) => System.Diagnostics.Trace.WriteLine("Raised");
var key = EventRaise.GetStaticNonPublicField(typeof(Control), "EventClick");
RaiseEvent<EventHandler, EventArgs>(label, key, "Events", EventArgs.Empty);
}
イベントを発生させたいクラスはLabel
であるが、そのキーとなるオブジェクトEventClick
はControl
クラスにprivateで定義されているので、オブジェクトの取得時にtypeof(Control)
とする必要がある。
「Onイベント名」のメソッドを呼べばいいと割り切った場合
// ValueChangedはEventHandler型のイベント
protected void OnValueChanged(EventArgs e) => ValueChanged?.Invoke(this, e);
こういうメソッドが存在する場合は、単純にこのメソッドを呼べばよい。
イベントに対してこうしたメソッドを用意しておくことが.NETでは推奨されているので、かなり広範囲で使用できるはず。
public static void ExecuteOnMethod<TEventArgs>(
object target,
string methodName, // e.g. "OnEventName"
TEventArgs eventArgs)
{
var type = target.GetType();
var method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
method.Invoke(target, new[] { eventArgs });
}
使い方
// AはOnValueChangedメソッドを持ち、その中でValueChangedイベントを発生させる必要がある
var a = new A();
a.ValueChanged += (sender, e) => System.Diagnostics.Trace.WriteLine("Raised");
ExecuteOnMethod(a, "OnValueChanged", EventArgs.Empty);
参考にした情報
EventInfoを使ってイベントの発行を行う
https://smdn.jp/programming/dotnet-samplecodes/reflection/3a87ea19024111eb907175842ffbe222/
方法: イベント プロパティを使用して複数のイベントを処理する
https://learn.microsoft.com/ja-jp/dotnet/standard/events/how-to-handle-multiple-events-using-event-properties?redirectedfrom=MSDN