Elenco di qualsiasi oggetto T

2

Sto cercando di evitare il casting di qualsiasi tipo. Ho la seguente semplice classe Bus che puoi usare per registrare i tuoi gestori:

public interface IDomainEvent {}

public class Bus
{
    private static readonly IList<IHandler<IDomainEvent>> Handlers = new List<IHandler<IDomainEvent>>();

    public static void Register(IHandler<IDomainEvent> handler)
    {
        if (handler != null)
            Handlers.Add(handler);
    }

    public static void Raise<T>(T eventData) where T : IDomainEvent
    {
        foreach (var handler in Handlers)
        {
            handler.Handle(eventData);
        }
    }
}

public interface IHandler<T> 
{
    T Handle(T eventData);
}

public class UpdatePersonHandler : IHandler<Person>
{
    public Person Handle(Person eventData)
    {
        var test = eventData.Name;
        return eventData;
    }
}

Questo dà un errore perché non è di tipo IDomainEvent

Bus.Register(new UpdatePersonHandler());

Come posso ristrutturare il bus in modo che sia abbastanza generico da contenere QUALSIASI tipo di IHandler?

Ovviamente fare Bus<T> non è la soluzione giusta, dal momento che Bus dovrebbe gestire qualsiasi tipo di IHander

Ho provato questo (ancora errore):

public interface IHandler<T> where T : IDomainEvent
{
    T Handle(T eventData);
}

public class UpdatePersonHandler : IHandler<Person> 
{
    public Person Handle(Person eventData)
    {
        var test = eventData.Name;
        return eventData;
    }
}

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

Argument 1: cannot convert from 'ConsoleApplication1.UpdatePersonHandler' to 'ConsoleApplication1.IHandler'

public class Bus<T>
{
    private static readonly IList<IHandler<T>> Handlers = new List<IHandler<T>>();

    public static void Register(IHandler<T> handler)
    {
        if (handler != null)
            Handlers.Add(handler);
    }

    public static void Raise(T eventData)
    {
        foreach (var handler in Handlers) {
            handler.Handle(eventData);
        }
    }
}

Dopo questa modifica, il codice viene compilato e sono in grado di eseguire quanto segue:

    Bus<Person>.Register(new UpdatePersonHandler());
    Bus<Person>.Raise(new Person {Name = "John"});

Tuttavia, per quanto riguarda la concorrenza ?, cosa succede se voglio gestire Bus<Animal> ? allo stesso tempo. i vecchi valori in Bus.List<T> andrebbero persi-corretto?

    
posta Shane Km 17.08.2016 - 22:33
fonte

2 risposte

1

Considerando le limitazioni del sistema di scrittura di tipo C #. vincoli generici, e altre cose come contra vs covariance vs invariance, ecc (che si intersecano anche con la propria scelta di progettazione del sistema di tipo CLR), credo che sarà difficile per te ottenere ciò, senza dover eseguire qualsiasi down casting ovunque . Tuttavia, potresti essere in grado di nasconderne almeno la maggior parte dal livello applicazione / client (ovvero, il consumatore dell'API bus controllato in modo statico) utilizzando i vincoli generici dipendenti; ad es. qualcosa sulla falsariga di:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

/// <summary>
/// SE 328643:
/// List of any object T
/// http://programmers.stackexchange.com/questions/328643/list-of-any-object-t
/// </summary>
public interface IDomainEvent { }

public interface IHandler<T> where T : IDomainEvent
{
    T Handle(T eventData);
}

public class Bus<T> where T : IDomainEvent
{
    private static IList<IHandler<T>> handlers = new List<IHandler<T>>();

    public static void Register<H>(H handler) where H : IHandler<T>
    {
        handlers.Add(handler);
    }

    public static void Raise(T eventData)
    {
        foreach (var handler in handlers)
        {
            handler.Handle(eventData);
        }
    }
}

public delegate void BusDispatch<T>(T eventData) where T : IDomainEvent;

public class AnyBus
{
    private static IDictionary<Type, Delegate> busDispatch =
        new Dictionary<Type, Delegate>
        {
            { typeof(Bus<Person>), (BusDispatch<Person>)Bus<Person>.Raise },
            { typeof(Bus<Thing>), (BusDispatch<Thing>)Bus<Thing>.Raise }
        };

    public static void Raise<T>(T eventData) where T : IDomainEvent
    {
        var busType = typeof(Bus<>).MakeGenericType(typeof(T));
        busDispatch[busType].DynamicInvoke(eventData);
    }
}

public class SomeBase
{
    public override string ToString() { return Data != null ? Data.ToString() : string.Empty; }
    public object Data { get; set; }
}

public class Person : SomeBase, IDomainEvent { }

public class Phase1PersonHandler : IHandler<Person>
{
    public Person Handle(Person person)
    {
        Console.WriteLine("Phase #1 {0}...", person);
        return person;
    }
}

public class Phase2PersonHandler : IHandler<Person>
{
    public Person Handle(Person person)
    {
        Console.WriteLine("Phase #2 {0}...", person);
        return person;
    }
}

public class Thing : SomeBase, IDomainEvent { }

public class ThingHandler : IHandler<Thing>
{
    public Thing Handle(Thing thing)
    {
        Console.WriteLine("Handling {0}...", thing);
        return thing;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Bus<Person>.Register(new Phase1PersonHandler());
        Bus<Person>.Register(new Phase2PersonHandler());
        Bus<Thing>.Register(new ThingHandler());

        AnyBus.Raise(new Person { Data = "Person 1" });
        AnyBus.Raise(new Thing { Data = "Thing 1" });
        AnyBus.Raise(new Person { Data = "Person 2" });

        Console.ReadKey();
    }
}

(dove l'unico downcast che si verifica è limitato in quel campo statico privato AnyBus.busDispatch sopra e nell'implementazione AnyBus.Raise)

Testabile qui:

link

Un altro potenziale problema di cui essere consapevoli con questo tipo di soluzione, tuttavia, è affidarsi a Delegate.DynamicInvoke, notoriamente lento rispetto alle invocazioni di delegati controllati in modo statico, quindi potrebbe trattarsi di un problema reale se si avere molte di chiamate in quel AnyBus.Raise rispetto al resto della tua applicazione.

[EDIT] Un altro avvertimento di quanto sopra è ovviamente non thread-safe, quindi ho appena dato uno spunto a quello qui sotto:

link

(Cfr. commenti di codice rilevanti)

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

/// <summary>
/// SE 328643:
/// List of any object T
/// http://programmers.stackexchange.com/questions/328643/list-of-any-object-t
/// </summary>
public interface IDomainEvent { }

public interface IHandler<T> where T : IDomainEvent
{
    // Btw, why this T return type (instead of void) if we ignore it in Bus<T>.Raise(...) anyway?
    T Handle(T eventData);
}

public class Bus<T> where T : IDomainEvent
{
    private static readonly ConcurrentBag<IHandler<T>> handlers = new ConcurrentBag<IHandler<T>>();

    /// <summary>
    /// Register a IHandler<T> handler for a Bus<T>
    /// (assuming the latter has a BusDispatch<T>, itself, already registered)
    /// </summary>
    public static void RegisterHandler<H>(H handler) where H : IHandler<T>
    {
        handlers.Add(handler);
    }

    /// <summary>
    /// Invoke all the registered IHandler<T>'s (if any) for the given event data, typed T
    /// </summary>
    public static void Raise(T eventData)
    {
        // Make sure the current thread is going to enumerate a frozen collection:
        var handlers = Bus<T>.handlers.Reverse().ToArray();
        // Now, enumerate:
        foreach (var handler in handlers)
        {
            handler.Handle(eventData);
        }
    }
}

public delegate void BusDispatch<T>(T eventData) where T : IDomainEvent;

public class AnyBus
{
    private static readonly ConcurrentDictionary<Type, Delegate> busDispatch =
        new ConcurrentDictionary<Type, Delegate>
        (
            new[]
            {
                // Let AnyBus know about a few hard-wired (aka "builtin") (BusDispatch<?>) Raise delegates
                new KeyValuePair<Type, Delegate>(typeof(Bus<Person>), (BusDispatch<Person>)Bus<Person>.Raise),
                new KeyValuePair<Type, Delegate>(typeof(Bus<Thing>), (BusDispatch<Thing>)Bus<Thing>.Raise)
            }
        );

    private static Type GetBusType<T>() where T : IDomainEvent
    {
        return typeof(Bus<>).MakeGenericType(typeof(T));
    }

    private static void UnsupportedEventDataError(Unsupported unsupported)
    {
        var eventData = unsupported.WrappedEventData;
        throw new InvalidOperationException(string.Format("Unsupported event data: {0}", (eventData != null ? eventData.GetType() : typeof(void)).Name));
    }

    internal class Unsupported : IDomainEvent
    {
        internal object WrappedEventData { get; set; }
    }

    /// <summary>
    /// Register a (BusDispatch<T>) Raise delegate for a given event data type T,
    /// or keep whichever existing one
    /// </summary>
    public static void RegisterDispatch<T>() where T : IDomainEvent
    {
        Func<Type, Delegate, Delegate> keepExisting = (type, @delegate) => @delegate;
        var busType = GetBusType<T>();
        busDispatch.AddOrUpdate(GetBusType<T>(), (BusDispatch<T>)Bus<T>.Raise, keepExisting);
    }

    public static void Raise<T>(T eventData) where T : IDomainEvent
    {
        var busType = GetBusType<T>();
        // We choose to use TryAdd(... UnsupportedEventData) as we want to be liberal enough
        // if it just so happens that another thread has just registered the (BusDispatch<T>) Raise we're precisely
        // interested in (for the provided event data, typed T);
        // however, we probably still *do* want to inform the application if it forgot to use,
        // AnyBus.RegisterDispatch<T>() -- throughout *all* threads -- prior to calling AnyBus.Raise
        busDispatch.TryAdd(busType, (BusDispatch<Unsupported>)UnsupportedEventDataError);
        if (!(busDispatch[busType] is BusDispatch<Unsupported>))
        {
            busDispatch[busType].DynamicInvoke(eventData);
        }
        else
        {
            UnsupportedEventDataError(new Unsupported { WrappedEventData = eventData });
        }
    }
}

public class SomeBase
{
    public override string ToString() { return Data != null ? Data.ToString() : string.Empty; }
    public object Data { get; set; }
}

public class Person : SomeBase, IDomainEvent { }

public class Phase1PersonHandler : IHandler<Person>
{
    public Person Handle(Person person)
    {
        Console.WriteLine("Phase #1 {0}...", person);
        return person;
    }
}

public class Phase2PersonHandler : IHandler<Person>
{
    public Person Handle(Person person)
    {
        Console.WriteLine("Phase #2 {0}...", person);
        return person;
    }
}

public class Thing : SomeBase, IDomainEvent { }

public class ThingHandler : IHandler<Thing>
{
    public Thing Handle(Thing thing)
    {
        Console.WriteLine("Handling {0}...", thing);
        return thing;
    }
}

public class SomethingElse : SomeBase, IDomainEvent { }

public class SomethingElseHandler : IHandler<SomethingElse>
{
    public SomethingElse Handle(SomethingElse somethingElse)
    {
        Console.WriteLine("Also handling {0}...", somethingElse);
        return somethingElse;
    }
}

class Program
{
    static void Main(string[] args)
    {
        // AnyBus already knows about how to dispatch the Person and Thing domain event
        // (no need to call AnyBus.Register<?>() for those privileged ones)
        Bus<Person>.RegisterHandler(new Phase1PersonHandler());
        Bus<Person>.RegisterHandler(new Phase2PersonHandler());
        Bus<Thing>.RegisterHandler(new ThingHandler());

        // Now, pretend we forgot to write the following two lines -- especially the first one:
        //AnyBus.RegisterDispatch<SomethingElse>();
        //Bus<SomethingElse>.RegisterHandler(new SomethingElseHandler());

        try
        {
            AnyBus.Raise(new Person { Data = "Person 1" });
            AnyBus.Raise(new Thing { Data = "Thing 1" });
            AnyBus.Raise(new Person { Data = "Person 2" });
            AnyBus.Raise(new SomethingElse { Data = "Something else" });
        }
        catch (Exception ex)
        {
            Console.WriteLine("Ouch:\r\n{0}", ex.Message);
        }

        //Console.ReadKey();
    }
}

'HTH,

    
risposta data 26.08.2016 - 20:50
fonte
0

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;
    }
}
    
risposta data 26.09.2016 - 04:18
fonte

Leggi altre domande sui tag