1

クラスの外からイベントを発生させる必要があるので、リフレクションが解決策だと思いました。ただし、イベントMultiCastDelegateとその呼び出しメソッドを取得した後は、それを呼び出すことができません。するとTargetException、「オブジェクトがターゲット タイプと一致しません」というメッセージが表示されます。

問題を再現するためのコードを次に示します。

ヘルパー クラス:

public static class Reflection
{
    public static MethodInfo GetEventMethod(this object obj, string eventName, out Type targetType)
    {
        if (obj == null) throw new ArgumentNullException("obj");
        if (string.IsNullOrEmpty(eventName)) throw new ArgumentNullException("eventName");

        var mcDelegate = getEventMulticastDelegate(obj, eventName, out targetType);
        return mcDelegate != null ? mcDelegate.Method : null;
    }

    private static MulticastDelegate getEventMulticastDelegate(object obj, string eventName, out Type targetType)
    {
        // traverse inheritance tree looking for the specified event ...
        targetType = obj.GetType();
        while (true)
        {
            var fieldInfo = targetType.GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetField);
            if (fieldInfo != null)
                return fieldInfo.GetValue(obj) as MulticastDelegate;

            if (targetType.BaseType == null)
                return null;

            targetType = targetType.BaseType;
        }
    }

    public static void RaiseEvent(this object obj, string eventName, params object[] parameters)
    {
        Type targetType;
        var methodInfo = obj.GetEventMethod(eventName, out targetType);
        if (methodInfo == null)
            throw new ArgumentOutOfRangeException("eventName", string.Format("Cannot raise event \"{0}\". Event is not supported by {1}", eventName, obj.GetType()));

        var targetObj = obj;
        if (targetType != obj.GetType())
            targetObj = obj.ConvertTo(targetType);

        methodInfo.Invoke(targetObj, parameters);  // *** ERROR HERE ***
    }
}

そしてそれをテストするためのいくつかのコード:

[TestClass]
public class Reflection
{
    [TestMethod]
    public void RaiseReflectedEvent()
    {
        var bar = new Bar();
        var isRaised = false;
        bar.TestEvent += (sender, args) => isRaised = true;
        bar.RaiseEvent("TestEvent", this, new EventArgs());
        Assert.IsTrue(isRaised);
        var foo = new Foo();
        isRaised = false;
        foo.TestEvent += (sender, args) => isRaised = true;
        foo.RaiseEvent("TestEvent", this, new EventArgs());
        Assert.IsTrue(isRaised);
    }

    private class Bar
    {
        public event EventHandler TestEvent;
    }

    private class Foo : Bar
    {
    }
}

私は何かを見逃したに違いありませんが、現在手がかりがなく、ヒントをいただければ幸いです。

4

2 に答える 2

3

まず第一に、これは非常に悪い設計であると言わなければなりません。イベントの呼び出しは意図的に制限されています。オブジェクトになりすまして、その同意なしにそのイベントを発生させることは、イベント所有者と任意のイベント リスナーの両方に対するあらゆる種類の仮定を破っています。本番コードでは決してこれを行うべきではありません。

第二に、あなたが取っているアプローチは一般的にはうまくいきません。イベントにはフィールドが関連付けられている必要はなく、そのフィールドはイベントと同じ名前である必要はありません。以下は有効な C# です。

class Foo {
    public event EventHandler Bar { add { } remove { } }
}

(このフォームは通常、あるイベントを別のイベントにリダイレクトするために使用されます)

第 3 に、上記の結果として、フィールドが見つかったとしても、それはイベントを呼び出す適切な方法ではない可能性があります。Raise特定の言語 (C++/CLI など) では、イベントを発生させるメソッドにイベントを明示的に関連付けることができます。メソッドを経由せずにデリゲートを呼び出すRaiseと、重大な論理エラーになる可能性があります。

ただし...これを行わない非常に強力な理由をすべて無視することを選択した場合、コードの問題は次のとおりです。

ADelegateは単なるメソッド参照ではありません。これはメソッド参照であり、メソッドを呼び出すために必要なコンテキストです。

次のコードがあるとします。

class Foo {
    void Bar() { }
    static void Main() {
        Foo foo = new Foo();
        Action action = new Action(foo.Bar);
    }
}

action.Methodは への参照になりますがFoo.Baraction.Targetは になりますfoo。あなたのGetEventMethod機能では、ターゲットを捨てています! デリゲートは、' this' パラメーターとしてメソッドに渡すオブジェクトを認識していますが、それを無視しています。実際、あなたの場合、ラムダ式を使用しているため、引数bar.TestEvent += (sender, args) => isRaised = true;' this' はコンパイラによって生成されたクロージャ オブジェクトになります。そのオブジェクトへの参照を期待できる唯一の方法はTarget、デリゲートのオブジェクトを使用することです。

Delegateしたがって、問題は、クラスを再実装しようとしているが、それも行っていないことです。デリゲート インスタンス自体でDynamicInvokeを呼び出すだけです。

もちろん、これをしないでください。イベント呼び出しは、それらを定義する型に対してプライベートです (その型がそれらを公開することを選択しない限り)。そうでないことを意図していた場合、型は型指定されたDelegateプロパティを公開しただけです。

于 2013-03-28T16:54:17.427 に答える