最初に使用した後、イベントの購読を解除する良い方法が見つかりませんでした。(確かにリフレクションを多用するアプローチを使用することもできますが、リファクタリングによってイベントの名前が変更された場合にコンパイラーが文句を言うことはないと思います)。
これはデリゲートのみを使用するものであるため、コンパイラーは引き続き適切に機能します。必要なほど軽量ではないかもしれませんが、自分で啓蒙に挑戦したので、シェアしたいと思いました。
MyEvent += SingleUseEventHandler<AssemblyLoadEventArgs, AssemblyLoadEventHandler>
.Create(This_MyEventOccurred);
ここで魔法が定義されている場所:
public class SingleUseEventHandler<TArgs,THandler>
where TArgs : EventArgs
{
public static THandler Create(EventHandler<TArgs> handler)
{
var helper = new SingleUseEventHandler<TArgs, THandler>(handler);
EventHandler<TArgs> h = helper.InvokeIfFirstTime;
return (THandler)(object)Delegate.CreateDelegate(typeof(THandler), h.Target, h.Method);
}
public void InvokeIfFirstTime(object sender, TArgs args)
{
if (!raised)
{
raised = true;
handler(sender, args);
}
}
public SingleUseEventHandler(EventHandler<TArgs> handler)
{
this.handler = handler;
}
bool raised;
readonly EventHandler<TArgs> handler;
}
もちろん、C#はデリゲートの種類を推測しないため、明示的に指定する必要があります。
イベントの定義がEventHandlerの場合、代わりにこれを使用できます。
MyEvent += SingleUseEventHandler<SomeEventArgs>.Create(SomeHandlerMethod);
public static class SingleUseEventHandler<TArgs>
where TArgs : EventArgs
{
public static EventHandler<TArgs> Create(EventHandler<TArgs> handler)
{
var helper = new SingleUseEventHandler<TArgs, EventHandler<TArgs>>(handler);
return helper.InvokeIfFirstTime;
}
}
プログラムの例を次に示します。
class Program
{
static event AssemblyLoadEventHandler MyEvent;
static int callCount;
static void Main(string[] args)
{
MyEvent += SingleUseEventHandler<AssemblyLoadEventArgs, AssemblyLoadEventHandler>
.Create(Load);
foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Console.WriteLine("Raising event for " + assembly.GetName().Name);
MyEvent(null, new AssemblyLoadEventArgs(assembly));
}
}
static void Load(object sender, AssemblyLoadEventArgs eventArgs)
{
Console.WriteLine(++callCount);
}
}