事件を対象として伝える

9858 ワード

最近、APIのデザインと関係があることを考えています.API設計は言語特性に関係することが多いため、Javaのような言語は、API設計時に抑圧されることが多い.C#はMoqやFluent NHIbernateのようなプロジェクトを出すことができます.同様に、F#はFsTestを開発することができ、ScalaはScalable Languageと呼ばれ、豊富な言語特性に頼っている.しかし、最近ではC#を使っているときに鼻に灰が少し当たったのは、「事件」というものが相手に伝わらないことに気づいたからだ.
public class Program
{
    public event EventHandler Submit;
}

このイベントに処理関数を追加するには、次のようにします.
var myClass = new MyClass();
myClass.Submit += (sender, eventArgs) => Console.WriteLine(sender);

ただし、「統合追加」の補助関数を書きたい場合は、たとえば次のように呼び出すことができます.
RegisterHandlers(myClass.Submit);

できないことに気づく.しかし、このようなRegisterHandlersメソッドの実装を提供すれば、
class Program
{
    public event EventHandler Submit;

    static void RegisterHandlers(EventHandler ev)
    {
        ev += (sender, eventArgs) => Console.WriteLine("sender");
    }

    static void Main(string[] args)
    {
        Program p = new Program();
        RegisterHandlers(p.Submit);

        p.Submit("Hello World", EventArgs.Empty);
    }
}

これはコンパイル可能で、......まあまあのようです.しかし、実際に実行すると、p.SubmitイベントがトリガーされたときにNull ReferenceException異常が放出されることがわかります(なぜですか?)そのため、私たちは別の方法を選択しなければなりません.
イベントとはいえ,処理関数を登録・削除する際にaddメソッドとremoveメソッドを呼び出すことが知られている.たとえば、このコード:
myClass.Submit += (sender, eventArgs) => Console.WriteLine(sender);

次のコードとは「等価」です.
myClass.add_Submit((sender, eventArgs) => Console.WriteLine(sender));

等価に引用符を打つのはadd_Submitという行のコードは実はコンパイルできません.私はただ意味を表すために使います.しかし、これは、addメソッドとremoveメソッドを反射によって呼び出すことができることを意味する.そこで、私はこのようなクラスを書きました.
public class Event
{
    public Event(Expression<Func> eventExpr)
    {
        ...
    }

    private object m_instance;
    private MethodInfo m_addMethod;
    private MethodInfo m_removeMethod;

    public Event AddHandler(T handler)
    {
        this.m_addMethod.Invoke(this.m_instance, new object[] { handler });
        return this;
    }

    public Event RemoveHandler(T handler)
    {
        this.m_removeMethod.Invoke(this.m_instance, new object[] { handler });
        return this;
    }
}

そこで、イベントをオブジェクトにカプセル化することができます.
class Program
{
    public event EventHandler Submit;

    static void Main(string[] args)
    {
        Program p = new Program();
        var ev = new Event<EventHandler>(() => p.Submit);
        ev.AddHandler((sender, eventArgs) => Console.WriteLine(sender));

        p.Submit("Hello World", EventArgs.Empty);
    }
}

ではEventクラスのコンストラクション関数はどのように書くのでしょうか.解析式ツリーにすぎません.
public Event(Expression<Func> eventExpr)
{
    var memberExpr = eventExpr.Body as MemberExpression;
    this.m_instance = memberExpr.Expression == null ? null :
        Expression.Lambda<Func<object>>(memberExpr.Expression).Compile()();

    var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod |
        (this.m_instance == null ? BindingFlags.Static : BindingFlags.Instance);

    var member = memberExpr.Member;
    this.m_addMethod = member.DeclaringType.GetMethod("add_" + member.Name, bindingFlags);
    this.m_removeMethod = member.DeclaringType.GetMethod("remove_" + member.Name, bindingFlags);
}

()=>p.Submitのようなコードでは、MemberExpressionであり、MemberExpressionのプロパティでpのインスタンスを使用することができます.その後、Submit属性のメンバーのNameに基づいてaddメソッドまたはremoveメソッドが得られます.インスタンスイベントなのか静的イベントなのかを再判断すればよい.全体的に、コードは簡単です.もちろん,実際の運用では不合法な場合に適切な異常を投げ出すことが要求される.また、パフォーマンスに必要な場合は、FastLambdaであるFastReflectionLibを使用してパフォーマンスを向上させることもできます.
使いやすいように、+と-の2つのオペレータと1つのEventFactoryクラスを再ロードしました.
public static class EventFactory
{
    public static Event Create(Expression<Func> eventExpr)
    {
        return new Event(eventExpr);
    }
}

public class Event
{
    ...

    public static Event operator +(Event ev, T handler)
    {
        return ev.AddHandler(handler);
    }

    public static Event operator -(Event ev, T handler)
    {
        return ev.RemoveHandler(handler);
    }
}

EventFactoryクラスのCreateメソッドは、Tタイプを明示的に提供することを避けることができますが、+オペレータと-オペレータの目的は、イベント処理関数を追加および削除する際に「より同じこと」を行うことです.このようなコードを書くことができます
class Program
{
    public event EventHandler Submit;

    static void Main(string[] args)
    {
        Program p = new Program();
        var ev = EventFactory.Create(() => p.Submit);
        ev += (sender, eventArgs) => Console.WriteLine(sender);

        p.Submit("Hello World", EventArgs.Empty);

        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }
}

Eventオブジェクトがある以上、パラメータとして他のメソッドに渡し、他のメソッドでイベント処理関数を追加または削除できます.
素晴らしいのではないでしょうか.あなたも完全なコードをダウンロードしてみてください.そして、はっきり言えません.この方法の罠を見つけることができます.私は認めて、実はこの解決策はC#の1つの問題に出会って、それは私をからかって、みんなをからかって......