2

ここからコードをコピーしました:https ://whathecode.wordpress.com/2012/03/26/null-checks-for-event-handlers-an-aspect-solution/

しかし、イベントが一般的に型指定されたクラス内にある場合、それを機能させることができないようです。私は次のように定義されたクラスを持っています:

Public Class MultiKeyDictionary<TFirstKey, TSecondKey, TValue>

および次のイベント:

public delegate void EventDelegate(TValue value);

public delegate void ReplacedEventDelegate(TValue oldValue, TValue newValue);

public event EventDelegate Added;

public event EventDelegate Removed;

public event ReplacedEventDelegate Replaced;

ただし、型のContainsGenericParametersがtrue(またはそれに類似したもの)に設定されていることを示す初期化コードの例外。

RuntimeInitializeメソッドのそのリンクのコードを次のように変更しました。

public override void RuntimeInitialize(EventInfo eventInfo) {
    base.RuntimeInitialize(eventInfo);
    Type eventType;
    MethodInfo delegateInfo = eventInfo.EventHandlerType.MethodInfoFromDelegateType();          
    ParameterExpression[] parameters = delegateInfo.GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
    if(eventInfo.EventHandlerType.ContainsGenericParameters) {
        var genericDelegate = eventInfo.EventHandlerType.GetGenericTypeDefinition();
        var genericParams = genericDelegate.GetGenericArguments();
        eventType = genericDelegate.MakeGenericType(genericParams);
    } else {
        eventType = eventInfo.EventHandlerType;
    }
    Delegate emptyDelegate = Expression.Lambda(eventType, Expression.Empty(), "EmptyDelegate", true, parameters).Compile();
    this.addEmptyEventHandler = instance => eventInfo.AddEventHandler(instance, emptyDelegate);
}

しかし、私が今得ているのはArgumentExceptionだけです。emptyDelegateを作成する行で、タイプ'TValue'のParameterExpressionをタイプ'TValue'のデリゲートパラメーターに使用することはできません。

4

1 に答える 1

1

以前ブログで返信したように、ここでの主な問題は、RuntimeInitialize()PostSharpが呼び出されたときに、クラスが初期化される一般的な引数をまだ認識していないことです。ただし、OnConstructorEntry()が呼び出された場合、この情報があります。PostSharpアスペクトの動作の詳細については、アスペクトの有効期間に関するドキュメントを必ずお読みください。

既存のコードでは、実行時にクラス(RuntimeInitialize()メソッド)のアスペクトが作成されるときに、クラスがジェネリックである可能性があるという事実を単に無視しました。これは私の側の見落としでした。を使用して「ジェネリック」型をコンパイルすることはできませんExpression.Lambda。したがって、ジェネリック型のすべての異なるインスタンス化で使用できる共通のイベントハンドラーをコンパイルすることはできません。

ジェネリック型のさまざまなインスタンス化ごとに、実行時にこの空のイベントハンドラーを個別にコンパイルする必要があります。これは、PostSharpによって渡されOnConstructorEntryたパラメーターからインスタンスタイプを受け取ることができる場所で実行できます。MethodExecutionArgs

ハンドラーを追加する必要があるイベントを知るにEventInfoは、実行時の初期化時にアスペクトに格納する必要があります。

[NonSerialized]
EventInfo _event;

名前を比較することで、アスペクトがどのイベントに適用されるかがわかります。以下は、現在動作しているからのコードですOnConstructorEntry()

Type runtimeType = args.Instance.GetType();
EventInfo runtimeEvent =
    runtimeType.GetEvents().Where( e => e.Name == _event.Name ).First();

MethodInfo delegateInfo =
    DelegateHelper.MethodInfoFromDelegateType( runtimeEvent.EventHandlerType );
ParameterExpression[] parameters = delegateInfo
    .GetParameters()
    .Select( p => Expression.Parameter( p.ParameterType ) )
    .ToArray();
Delegate emptyDelegate = Expression.Lambda(
    runtimeEvent.EventHandlerType, Expression.Empty(),
    "EmptyDelegate", true, parameters ).Compile();

// Add the empty handler to the instance.
MethodInfo addMethod = runtimeEvent.GetAddMethod( true );
if ( addMethod.IsPublic )
{
    runtimeEvent.AddEventHandler( args.Instance, emptyDelegate );
}
else
{
    addMethod.Invoke( args.Instance, new object[] { emptyDelegate } );
}

これはまだ1つの問題を開いたままにします。タイプが作成されるたびに、このすべてのリフレクションを実行する必要はありません。したがって、理想的には、以前のように空のハンドラーを追加するメソッドをキャッシュする必要がありますRuntimeInitialize()。アスペクトコードはジェネリック型のすべてのインスタンス化によって「共有」されるため(同じスコープを使用します)、インスタンス型ごとに個別にキャッシュする必要があります。たとえば、を使用しますDictionary<Type, Action<object>>。ここで、Typeはインスタンスタイプを参照しAction<object>、空のイベントハンドラをインスタンスに追加できるメソッドです。

これはまさに私が現在ライブラリで使用している実装であり、更新されたバージョンはgithubで見つけることができますCachedDictionaryご覧のとおり、このような一般的なシナリオであるため、キャッシュロジックのほとんどをすでに処理するクラスを使用しています。以前は失敗していた単体テストが成功します。

于 2012-11-05T17:17:42.873 に答える