C # pratiche statiche provenienti da sfondo dinamico

3

Sono stato dappled in C # dopo essere venuto da diversi anni in PHP. Non trovo la lingua particolarmente difficile, sebbene ci siano molti più costrutti su cui abituarmi.

La mia domanda è di aiutarmi a cambiare il modo di pensare quando uso C #. Più specificamente, per aiutarmi a pensare a soluzioni in un linguaggio statico quando sono stato rovinato da uno dinamico.

Per il mio esperimento ho creato una semplice copia di EventSispatcher di Symfony, che viene utilizzata in questo modo:

// PHP
class Sample
{
    public function __construct()
    {
        $dispatcher = new EventDispatcher();
        $dispatcher->addListener("sample.event", array($this, "onEvent"));
        $dispatcher->dispatch   ("sample.event", new Event());
    }

    public function onEvent(Event $event)
    {
        echo "Event Dispatched";
    }
}

E questa è la mia replica in C # di EventDispatcher:

// C#
public delegate void EventListener (EventArgs args);

public class EventDispatcher
{
    protected Dictionary<string, EventListener> Listeners = new Dictionary<string, EventListener> ();

    public void AddListener (string eventName, EventListener listener)
    {
        EventListener l;
        Listeners.TryGetValue (eventName, out l);

        if (null == l) {
            l = listener;
            Listeners.Add (eventName, l);
        } else {
            l += listener;
        }
    }

    public void Dispatch(string eventName, EventArgs args)
    {
        EventListener listener;
        Listeners.TryGetValue (eventName, out listener);

        if (null != listener) {
            listener (args);
        }
    }
}

E finalmente questa è una copia dell'esempio PHP:

// C#
class Application
{
    protected static EventDispatcher Dispatcher = new EventDispatcher();

    public static void Main(string[] args)
    {
        Dispatcher.AddListener ("sample.event", onEvent);
        Dispatcher.Dispatch    ("sample.event", new ErrorEventArgs(new Exception()));
    }

    public static void onEvent(EventArgs args)
    {
        Console.Out.WriteLine ("Event Dispatched");
    }
}

Ora la differenza è che sto inviando l'evento con ErrorEventArgs , una classe figlia di EventArgs . Questo va bene perché l'istanza è sostituita.

Tuttavia, in PHP posso dichiarare il listener come questo per accertarmi che l'evento corretto sia dato al metodo:

// PHP
public function onEvent(ErrorEvent $event)
{
    echo "Event Dispatched";
}

Tuttavia lo stesso in C # non funzionerà:

// C#
public static void onEvent(ErrorEventArgs args)
{
    Console.Out.WriteLine ("Event Dispatched");
}

Perché non corrisponde al delegato delegate void EventListener (EventArgs args) .

Ora so che .NET ha già un sistema di eventi e sembra che sto reinventando la ruota, anche se hai familiarità con Symfony2 saprai che è un dispatcher di eventi non un tipico esempio di sé (gli eventi sono generalmente globalizzati) .

Potrei in questo caso definire solo i metodi per conformarsi con il delegato richiesto e usare l'introspezione per determinare se l'evento dovrebbe essere gestito. Oppure posso implementare AddListener e Dispatch come metodi generici, a costo di definire il dizionario come Dictionary<string, object> .

Quindi la mia domanda si riduce a se mi sto avvicinando a questo nel modo sbagliato, se questo tipo di programmazione semi-dinamica è adatto, o c'è un modo veramente statico di implementarlo?

Tieni a mente che questo è un esperimento di esempio e nel mondo reale cercherò pratiche tradizionali per implementare questo specifico scenario.

EDIT:

Utilizzando la soluzione del dizionario di oggetti e generici, ecco il refactoring:

public delegate void EventListener<T> (T args);

public class EventDispatcher
{
    protected Dictionary<string, object> Listeners = new Dictionary<string, object> ();

    public void AddListener<T> (string eventName, EventListener<T> listener)
    {
        EventListener<T> l;
        object obj;

        if (Listeners.TryGetValue (eventName, out obj)) {
            l = (EventListener<T>)obj; // handling required here if obj cannot be casted.
            l += listener;
        } else {
            l = listener;
            Listeners.Add (eventName, l);
        }
    }

    public void Dispatch<T> (string eventName, T args)
    {
        EventListener<T> l;
        object obj;

        if (Listeners.TryGetValue (eventName, out obj)) {
            l = (EventListener<T>)obj;
            l (args);
        }
    }
}
    
posta Flosculus 16.12.2014 - 10:33
fonte

2 risposte

1

Una delle cose più importanti quando si utilizza un linguaggio di programmazione è capire quali sono le sue debolezze. Poiché molte lingue hanno punti deboli sostanzialmente diversi, è spesso una pessima idea trasferire direttamente il codice da uno all'altro.

In entrambi gli esempi forniti, viene presentato un problema di digitazione piuttosto grave. Anche il dizionario ha un grosso punto debole, perché è possibile passare qualsiasi chiave con qualsiasi tipo, il che significa che è probabile che il cast sia fallito.

Se capisco qual è il tuo obiettivo, stai provando a creare un sistema per eventi che funzionano attraverso abbonamenti e pubblicazioni. L'esempio migliore che ho visto di questo è EventAggregator di PRISM. Funziona in questo modo:

public class EventAggregator : IEventAggregator
{
  private List<PubSubEvent> events;
  public T Get<T>() where T : PubSubEvent, new()
  {
    var matchingEvent = events.OfType<T>.FirstOrDefault()
    if(matchingEvent != null)
      return matchingEvent;
    matchingEvent = new T();
    events.Add(matchingEvent);
    return matchingEvent;
  }
}

Dove PubSubEvent è qualcosa del tipo:

public abstract class PubSubEvent
{
  private List<Action> actions;
  public void Publish()
  {
    foreach (var action in actions)
      action();
  }
  public void Subscribe(Action action)
  {
    actions.Add(action);
  }
}

In pratica, il Get sull'aggregatore di eventi è statico, ma preferisco l'iniezione di dipendenza, se possibile. Potrebbe non essere necessario l'aggregatore se si dispone di una buona iniezione di dipendenza. Anche l'evento secondario pub è un po 'diverso, perché le versioni PRISM accettano argomenti, il che è più simile a ciò che si desidera. Quel progetto è open source, quindi puoi anche guardarlo.

In ogni caso, un evento può essere una sottoclasse semplice e vuota, perché la cosa più importante è il nome del tipo. Ogni evento gestisce il suo tipo di argomento e, se vuoi iscriverti a più eventi, importa più eventi.

Usando MEF per l'iniezione delle dipendenze, per esempio, potresti fare qualcosa di simile a questo:

[ImportingConstructor]
public SomeClass(DatabaseChangedEvent changedEvent)
{
  changedEvent.Subscribe(ReloadData);
}
private void ReloadData()
{
  ...
}

Quindi è importante pensare se sia necessario o meno che tutti gli eventi siano gestiti dallo stesso oggetto. Potrebbe essere più sensato avere solo l'accesso agli eventi esatti di cui hai bisogno. Ci sono molti modi per farlo. È possibile creare facilmente la classe di eventi di base generica, in cui il tipo generico è la classe argomento e ogni sottoclasse lo specifica in modo specifico. Questa sarebbe una soluzione facile.

In genere non hai bisogno di cose globali in C #, e la digitazione statica può aiutarti un bel po 'se non provi a combatterlo.

    
risposta data 17.12.2014 - 17:54
fonte
2

Permettimi di fare una domanda: cosa succede se nella tua versione di PHP scriverei:

// PHP
class Sample
{
    public function __construct()
    {
        $dispatcher = new EventDispatcher();
        $dispatcher->addListener("sample.event", array($this, "onEvent"));
        $dispatcher->dispatch   ("sample.event", new WarnEvent()); // oops?
    }

    public function onEvent(ErrorEvent $event)
    {
        echo "Event Dispatched";
    }
}

Non ho esperienza con PHP, ma questo probabilmente causerebbe un qualche tipo di comportamento di errore. Nel migliore dei casi, puoi gestirlo, nel peggiore dei casi, passerai giorni a cercare di scovare l'errore.

Il sistema di tipo statico di

C # semplicemente non consente nemmeno di scrivere codice come quello per ragioni di cui sopra. Se davvero non ti dispiace questo tipo di comportamento, puoi scrivere:

public static void onEvent(EventArgs args)
{
    if (!(args is ErrorEventArgs))
    {
        // something really wrong just happened
    }
    Console.Out.WriteLine ("Event Dispatched");
}

E del tuo suggerimento di utilizzare i generici + object . Penso che questa potrebbe essere una buona idea. Finché object è completamente incapsulato all'interno del Dispatcher.

    
risposta data 16.12.2014 - 11:45
fonte

Leggi altre domande sui tag