おかしな質問ですが、何度も何度も書きたくないもののように思えます。そのため、ハッシュセットなどよりも、単一の汎用コンポーネントを使用したいと考えています。また、フォームの実装は通常スレッドに基づいているため、並行辞書を使用します。
このソリューションは、いくつかの異なる方法で改善できます。最も明らかに、処理もより一般的になり、0 パラメーター ハンドラーがなくなります。クリアランスのためにできるだけシンプルにしました。おそらく、2、3 日以内に、より完全なものをブログに投稿する予定です。もしそうなら、ここで情報を共有します。
私のソリューションには、(1) 一般的なフック クラスと (2) フォーム内の実装の 2 つの部分があります。現在、解決策は怠惰です。たとえば、イベント ハンドラをキューの先頭ではなく最後に配置しています。GetInvocationList または同様のものを使用して、これを修正できるはずです。
ジェネリック フック クラスは、基本的にイベントをフックし、イベントが呼び出されたかどうかを追跡します。
public class EventHooks
{
private class EventHooksEquality : IEqualityComparer<Tuple<string, object>>
{
public bool Equals(Tuple<string, object> x, Tuple<string, object> y)
{
return x.Item1.Equals(y.Item1) && object.ReferenceEquals(x.Item2, y.Item2);
}
public int GetHashCode(Tuple<string, object> obj)
{
return obj.Item1.GetHashCode();
}
}
private ConcurrentDictionary<Tuple<string, object>, bool> called =
new ConcurrentDictionary<Tuple<string, object>, bool>(new EventHooksEquality());
private abstract class BaseHookHandler
{
protected BaseHookHandler(object container, string eventName, EventHooks hooks)
{
this.hooks = hooks;
this.container = container;
this.eventName = eventName;
}
protected string eventName;
protected object container;
protected EventHooks hooks;
}
private class HookHandler<T1> : BaseHookHandler
{
public HookHandler(object container, string eventName, EventHooks hooks)
: base(container, eventName, hooks)
{
}
public void Handle(T1 t1)
{
hooks.called.TryAdd(new Tuple<string, object>(eventName, container), true);
}
}
private class HookHandler<T1, T2> : BaseHookHandler
{
public HookHandler(object container, string eventName, EventHooks hooks)
: base(container, eventName, hooks)
{
}
public void Handle(T1 t1, T2 t2)
{
hooks.called.TryAdd(new Tuple<string, object>(eventName, container), true);
}
}
// add more handlers here...
public void HookAll(object obj)
{
foreach (var eventHandler in obj.GetType().GetEvents())
{
Hook(obj, eventHandler.Name);
}
}
public void Hook(object obj, string eventHandler)
{
if (obj == null)
{
throw new Exception("You have to initialize the object before hooking events.");
}
// Create a handler with the right signature
var field = obj.GetType().GetEvent(eventHandler);
var delegateInvoke = field.EventHandlerType.GetMethod("Invoke");
Type[] parameterTypes = delegateInvoke.GetParameters().Select((a) => (a.ParameterType)).ToArray();
// Select the handler with the correct number of parameters
var genericHandler = Type.GetType(GetType().FullName + "+HookHandler`" + parameterTypes.Length);
var handlerType = genericHandler.MakeGenericType(parameterTypes);
var handlerObject = Activator.CreateInstance(handlerType, obj, eventHandler, this);
var handler = handlerType.GetMethod("Handle");
// Create a delegate
var del = Delegate.CreateDelegate(field.EventHandlerType, handlerObject, handler);
// Add the handler to the event itself
field.AddEventHandler(obj, del);
}
public bool IsCalled(object obj, string eventHandler)
{
return called.ContainsKey(new Tuple<string, object>(eventHandler, obj));
}
}
クラスでの使用は次のように行うことができます (例):
public Form1()
{
InitializeComponent();
hooks.HookAll(this);
// or something like: hooks.Hook(this, "Load");
hooks.Hook(button1, "Click");
}
private EventHooks hooks = new EventHooks();
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text =
string.Format("Load: {0}\r\nClick: {1}\r\nButton click: {2}\r\n",
hooks.IsCalled(this, "Load"),
hooks.IsCalled(this, "Click"),
hooks.IsCalled(button1, "Click"));
}