In primo luogo, la domanda è mal posta. Nella sua forma attuale, avrei votato per la chiusura perché l'obiettivo principale o l'ostacolo della domanda non sono ben spiegati.
La paura di lanciare in C # è completamente fuori luogo. Qualsiasi tentativo di soluzione alternativa si tradurrà semplicemente in un codice più complicato che sarà anche più lento. L'uso giudizioso della cancellazione del testo (eseguendo il casting su object
, e quindi indietro), insieme alla logica di guardia correttamente implementata che rende impossibile un cast non valido, rimuoverà i molti ostacoli discussi in questa domanda.
Vale la pena considerare l'uso della cancellazione di tipo, poiché IHandler<IDomainEvent>
e IHandler<Person>
sono considerati non appartenenti alla stessa gerarchia di ereditarietà - non sono correlati, per quanto riguarda l'ereditarietà. Il problema riguarda anche i delegati, come Action<object>
e Action<People>
.
Quindi, il dizionario di primo livello è una mappatura da Type
a un sottotitolo che gestisce tipi specifici di dati. Il dizionario di secondo livello, nel frattempo, dovrà trattare i suoi valori ( Action<?>
che sono noti per accettare il tipo di dati associato a questo livello) come opaco.
Prima di discutere la bozza di concetto, evidenzierò alcuni risultati del mio benchmarking, per spiegare perché i timori sono mal posti.
La parte più lenta del codice, risulta, è foreach (var handler in dict)
. Questo è lento anche se il dizionario ha zero, uno o più elementi.
La seconda parte più lenta è Directory
indexer (ricerca articoli). Questo è comprensibile perché la sua implementazione non è banale, e il costo della prestazione è il prezzo che paghiamo per poter archiviare più oggetti in un posto centrale.
Infine, la lentezza deriva dal metodo mancante che elabora le opportunità. Questo è di nuovo comprensibile perché è un prezzo che paghiamo per scrivere codice flessibile riutilizzabile.
Quello che segue è un proof-of-concept. Questo codice si evolve dal codice in risposta di YSharp , quindi è necessario comprendere appieno la risposta precedente prima di provare a capire il mio codice .
Ci sono molte parti che vale la pena sottolineare. Innanzitutto, la concorrenza è semplice se non si modifica la configurazione contemporaneamente: aggiunta o rimozione di gestori quando è in esecuzione un altro codice. È difficile se vuoi farlo dinamicamente e contemporaneamente. Anche se questo codice è indurito, vedrai le condizioni di gara in altre parti del tuo codice, a meno che tu non scriva il tuo codice dai primi principi basati su una definizione di vita dell'oggetto.
Questo codice fornisce un metodo in cui, se si prevede che un particolare tipo di evento T
si verifichi ripetutamente, l'utente può chiedere un delegato (nella forma di Action<T>
) che può essere memorizzato e richiamato ripetutamente. Invece di attraversare i due livelli di dizionari per ogni chiamata, questo delegato viene sintetizzato dalla composizione. Questo evita il problema di foreach
. Nei miei test di benchmark, questo tweak delle prestazioni è più significativo di qualsiasi altro problema di prestazioni mai discusso in questa domanda.
Questo codice fornisce anche un altro metodo in cui, dato un tipo di evento T
, non cercherà solo i gestori registrati per T
stesso, ma anche per le classi base di T
, così come i gestori registrati per qualsiasi interfaccia I
implementata da T
. Questi gestori sono anche composti in un unico delegato.
public class TypeErasedActionRegistry
{
private static System.Reflection.BindingFlags flagInvokePublicStatic =
System.Reflection.BindingFlags.InvokeMethod |
System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.Public;
private static ConcurrentDictionary<Type, ConcurrentBag<object>> actionForAll =
new ConcurrentDictionary<Type, ConcurrentBag<object>>();
public static void ThrowIfInvalidAction<T>(object typeErasedActionObject) where T : IDomainEvent
{
if (typeErasedActionObject == null ||
!(typeErasedActionObject is Action<T>))
{
throw new ArgumentException("Invalid action");
}
}
public static void Register<T>(Action<T> action) where T : IDomainEvent
{
var actionBag = actionForAll.GetOrAdd(typeof(T),
(Type _) => new ConcurrentBag<object>());
actionBag.Add(action);
}
public static void Call<T>(T data) where T : IDomainEvent
{
ConcurrentBag<object> actionBag;
actionForAll.TryGetValue(typeof(T), out actionBag);
if (actionBag != null)
{
foreach (var typeErasedAction in actionBag)
{
ThrowIfInvalidAction<T>(typeErasedAction);
Action<T> action = (Action<T>)typeErasedAction;
action(data);
}
}
}
/// <summary>
/// <para>
/// Given compile-time type T, retrieve and combine all handlers
/// that are registered to handle the type T.
/// </para>
/// <para>
/// This method does not search handlers which are registered under
/// any types or interfaces other than T itself. In other words,
/// this method does not consider whether T might be assignable to
/// these types or interfaces.
/// </para>
/// </summary>
/// <remarks>
/// To search for handlers registered to handle related types
/// such as base types of T or interfaces implemented by T, use
/// <code>ComposeActionAndBaseTypes()</code>.
/// </remarks>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static Action<T> ComposeAction<T>() where T : IDomainEvent
{
Action<T> combinedAction = null;
ConcurrentBag<object> actionBag;
actionForAll.TryGetValue(typeof(T), out actionBag);
if (actionBag != null)
{
foreach (var typeErasedAction in actionBag)
{
ThrowIfInvalidAction<T>(typeErasedAction);
Action<T> action = (Action<T>)typeErasedAction;
combinedAction = (combinedAction == null) ? (action) : (combinedAction + action);
}
}
return combinedAction;
}
/// <summary>
/// Given compile-time type T, search for handlers that accept either T,
/// or one of its base types, or one of the interfaces implemented by T.
/// Combine all qualifying handlers into a new Action delegate.
/// </summary>
/// <remarks>
/// <para>
/// If no qualifying handlers exist, null is returned.
/// </para>
/// <para>
/// If multiple qualifying handlers are found, they are combined in
/// unspecified, potentially non-deterministic order. User shall not
/// rely on any assumptions about specific ordering.
/// </para>
/// </remarks>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static Action<T> ComposeActionAndBaseTypes<T>() where T : IDomainEvent
{
Type currentType = typeof(T);
Action<T> combinedAction = null;
while (typeof(IDomainEvent).IsAssignableFrom(currentType))
{
var method = typeof(TypeErasedActionRegistry).GetMethod("ComposeAction", flagInvokePublicStatic);
var methodReified = method.MakeGenericMethod(currentType);
var composeActionResult = methodReified.Invoke(null, null);
if (composeActionResult != null)
{
Action<T> delegateForCurrentType = (Action<T>)composeActionResult;
combinedAction = (combinedAction == null) ? (delegateForCurrentType) : (combinedAction + delegateForCurrentType);
}
currentType = currentType.BaseType;
}
return combinedAction;
}
}