2

を使用して二重ディスパッチを実装するdynamic:

public interface IDomainEvent {}

public class DomainEventDispatcher
{
    private readonly List<Delegate> subscribers = new List<Delegate>();

    public void Subscribe<TEvent>(Action<TEvent> subscriber) where TEvent : IDomainEvent
    {
        subscribers.Add(subscriber);
    }

    public void Publish<TEvent>(TEvent domainEvent) where TEvent : IDomainEvent
    {
        foreach (Action<TEvent> subscriber in subscribers.OfType<Action<TEvent>>())
        {
            subscriber(domainEvent);
        }
    }

    public void PublishQueue(IEnumerable<IDomainEvent> domainEvents)
    {
        foreach (IDomainEvent domainEvent in domainEvents)
        {
            // Force double dispatch - bind to runtime type.
            Publish(domainEvent as dynamic);
        }
    }
}

public class ProcessCompleted : IDomainEvent { public string Name { get; set; } }

ほとんどの場合に機能します:

var dispatcher = new DomainEventDispatcher();

dispatcher.Subscribe((ProcessCompleted e) => Console.WriteLine("Completed " + e.Name));

dispatcher.PublishQueue(new [] { new ProcessCompleted { Name = "one" },
                                 new ProcessCompleted { Name = "two" } });

1つ完成

2つ完成

ただし、サブクラスがディスパッチ コードから見えない場合は、実行時エラーが発生します。

public static class Bomb
{
    public static void Subscribe(DomainEventDispatcher dispatcher)
    {
        dispatcher.Subscribe((Exploded e) => Console.WriteLine("Bomb exploded"));
    }
    public static IDomainEvent GetEvent()
    {
        return new Exploded();
    }
    private class Exploded : IDomainEvent {}
}
// ...

Bomb.Subscribe(dispatcher);  // no error here
// elsewhere, much later...
dispatcher.PublishQueue(new [] { Bomb.GetEvent() });  // exception

RuntimeBinderException

型「オブジェクト」は、ジェネリック型またはメソッド「DomainEventDispatcher.Publish(TEvent)」の型パラメーター「TEvent」として使用できません

これは不自然な例です。より現実的なものは、別のアセンブリの内部にあるイベントです。

この実行時例外を防ぐにはどうすればよいですか? それが不可能な場合、Subscribeメソッドでこのケースを検出してすばやく失敗するにはどうすればよいですか?

編集:動的キャストを排除するソリューションは、すべてのサブクラスを認識しているビジター スタイルのクラスを必要としない限り、許容されます。

4

4 に答える 4

2

この実行時例外を防ぐにはどうすればよいですか?

本当にできない、それが の性質ですdynamic

それが不可能な場合、Subscribeメソッドでこのケースを検出してすばやく失敗するにはどうすればよいですか?

typeof(TEvent).IsPublicサブスクライバーを追加する前に、おそらく確認できます。

dynamicそうは言っても、二重発送が本当に必要かどうかはわかりません。が で、 に基づいて の購読者を検索した場合subscribersはどうなるでしょうか?Dictionary<Type, List<Action<IDomainEvent>>>Publish(IDomainEvent domainEvent)domainEvent.GetType()

于 2016-02-25T23:23:49.057 に答える
1

動的呼び出しが失敗する理由を理解しようとするのではなく、実用的なソリューションを提供することに集中します。契約を理解する方法では、有効なサブスクライバーがあり、呼び出しをディスパッチできるはずだからです。

幸いなことに、動的でない呼び出しベースのソリューションがいくつかあります。

リフレクションによるPublishメソッドの呼び出し:

private static readonly MethodInfo PublishMethod = typeof(DomainEventDispatcher).GetMethod("Publish"); // .GetMethods().Single(m => m.Name == "Publish" && m.IsGenericMethodDefinition);

public void PublishQueue(IEnumerable<IDomainEvent> domainEvents)
{
    foreach (var domainEvent in domainEvents)
    {
        var publish = PublishMethod.MakeGenericMethod(domainEvent.GetType());
        publish.Invoke(this, new[] { domainEvent });
    }
}

リフレクションsubscriberによる呼び出し:

public void PublishQueue(IEnumerable<IDomainEvent> domainEvents)
{
    foreach (var domainEvent in domainEvents)
    {
        var eventType = typeof(Action<>).MakeGenericType(domainEvent.GetType());
        foreach (var subscriber in subscribers)
        {
            if (eventType.IsAssignableFrom(subscriber.GetType()))
                subscriber.DynamicInvoke(domainEvent);
        }
    }
}

Publishコンパイル済みのキャッシュされたデリゲートを介してメソッドを呼び出す:

private static Action<DomainEventDispatcher, IDomainEvent> CreatePublishFunc(Type eventType)
{
    var dispatcher = Expression.Parameter(typeof(DomainEventDispatcher), "dispatcher");
    var domainEvent = Expression.Parameter(typeof(IDomainEvent), "domainEvent");
    var call = Expression.Lambda<Action<DomainEventDispatcher, IDomainEvent>>(
        Expression.Call(dispatcher, "Publish", new [] { eventType },
            Expression.Convert(domainEvent, eventType)),
        dispatcher, domainEvent);
    return call.Compile();
}

private static readonly Dictionary<Type, Action<DomainEventDispatcher, IDomainEvent>> publishFuncCache = new Dictionary<Type, Action<DomainEventDispatcher, IDomainEvent>>();

private static Action<DomainEventDispatcher, IDomainEvent> GetPublishFunc(Type eventType)
{
    lock (publishFuncCache)
    {
        Action<DomainEventDispatcher, IDomainEvent> func;
        if (!publishFuncCache.TryGetValue(eventType, out func))
            publishFuncCache.Add(eventType, func = CreatePublishFunc(eventType));
        return func;
    }
}

public void PublishQueue(IEnumerable<IDomainEvent> domainEvents)
{
    foreach (var domainEvent in domainEvents)
    {
        var publish = GetPublishFunc(domainEvent.GetType());
        publish(this, domainEvent);
    }
}

デリゲートは、compiled を使用して遅延して作成され、オンデマンドでキャッシュされ System.Linq.Expressionsます。

これまでのところ、この方法が最も速いはずです。また、動的呼び出しの実装に最も近いですが、機能するという違いがあります:)

于 2016-03-02T19:27:07.830 に答える
1

Publish メソッドを次のように変更するだけです。

foreach(var subscriber in subscribers) 
    if(subscriber.GetMethodInfo().GetParameters().Single().ParameterType == domainEvent.GetType())
         subscriber.DynamicInvoke(domainEvent);

更新
また、呼び出しを変更する必要があります

 Publish(domainEvent); //Remove the as dynamic

この方法では、パブリッシュの署名を変更する必要はありません

私は他の答えを好みます: C# subscribe to events based on parameter type?

更新 2
ご質問について

元の呼び出しが失敗したときに、この動的な呼び出しが機能する理由が気になります。

dynamic は特別なタイプではないことに注意してください。
基本的にコンパイラ:
1) オブジェクトに置き換えます
2) コードをより複雑なコードにリファクタリングします
3) コンパイル時のチェックを削除します (これらのチェックは実行時に行われます)

交換しようとしたら

Publish(domainEvent as dynamic);

Publish(domainEvent as object);

同じメッセージが表示されますが、今回はコンパイル時に表示されます。エラーメッセージは自明です:

型「オブジェクト」は、ジェネリック型またはメソッド「DomainEventDispatcher.Publish(TEvent)」の型パラメーター「TEvent」として使用できません

最後の注意として。
dynamic は特定のシナリオ向けに設計されており、99,9% の時間は必要なく、静的に型指定されたコードに置き換えることができます。
必要だと思う場合 (上記の場合のように)、おそらく何か間違ったことをしている可能性があります

于 2016-02-28T19:27:34.097 に答える
-1

メソッドには既にジェネリック型があるためSubscribe、次の簡単な変更を行うことができます。

private readonly List<Action<object>> subscribers = new List<Action<object>>();

public void Subscribe<TEvent>(Action<TEvent> subscriber) where TEvent : class
{
    subscribers.Add((object evnt) =>
    {
        var correctType = evnt as TEvent;
        if (correctType != null)
        {
            subscriber(correctType);
        }
    });
}

public void Publish(object evnt)
{
    foreach (var subscriber in subscribers)
    {
        subscriber(evnt);
    }
}

パブリッシュ側とサブスクライブ側の両方でコンパイル時の型情報が欠落している場合でも、動的キャストを削除できます。この式構築の例を参照してください。

于 2016-02-29T22:20:06.823 に答える