Laden...

Multicast-Delegaten: Auswertung aller Rückgabewerte und Abbruchmöglichkeit

Erstellt von MarsStein vor 13 Jahren Letzter Beitrag vor 13 Jahren 5.871 Views
MarsStein Themenstarter:in
3.170 Beiträge seit 2006
vor 13 Jahren
Multicast-Delegaten: Auswertung aller Rückgabewerte und Abbruchmöglichkeit

Hallo Community,

ich habe eine Erweiterung zum Delegate-Typen geschrieben, die vielleicht für einige von Euch nützlich ist.

Beschreibung:
Die Methode ExecuteBreakable führt die einzelnen Delegaten eines Multicast-Delegaten sequenziell aus und ruft für jeden einzelnen nach dessen Ausführung eine Callbackfunktion vom Typ BreakCallback auf.
An diese Callback werden der Multicast-Delegat, der einzelne ausgeführte Delegat sowie sein Rückgabewert übergeben.

Gibt die Callback true zurück, werden weitere Delegaten in der Aufrufliste des Multicast-Delegaten nicht mehr ausgeführt.

So kann bei Multicast-Delegaten mit Rückgabewert z.B. aufgrund desselben über den Abbruch des Multicast entschieden werden.
Auch eine Statusausgabe oder ein Logging für die Abarbeitung der Aufrufliste eines Multicast-Delegaten könnte so realisiert werden.

public static class MulticastBreakExtension
{
  /// <summary>
  /// Delegat, der für jeden einzelnen Delegaten in der Multicast-Liste
  /// eines über ExecuteBreakable ausgeführten Delegaten aufgerufen wird.
  /// </summary>
  public delegate bool BreakCallback(Delegate multicast,
                                     Delegate exec,
                                     object execReturnValue);


  /// <summary>
  /// ExecuteBreakable - Erweiterungsmethode für Delegaten.
  /// Diese Methode ermöglicht, einen Multicast-Delegaten sequenziell
  /// auszuführen und in einer Callback-Methode
  /// a) die Rückgabewerte der Delegaten in der Multicast-Liste auszuwerten
  /// b) über den Abbruch des Multicast zu entscheiden.
  /// </summary>
  /// <param name="del">
  /// Der aufrufende/aufzuführende (Multicast-)Delegat.
  /// </param>
  /// <param name="callback">
  /// Eine Delegat von Typ BreakCallback.
  /// Diese Callback-Methode wird nach jedem Delegaten der Multicast-Liste
  /// aufgerufen. Der Multicast-Delegat, der ausgeführte Delegat und dessen
  /// Rückgabewert werden an die BreakCallback übergeben.
  /// Gibt die Callback-Methode true zurück, wird der Multicast abgebrochen.
  /// </param>
  /// <param name="args">
  /// Argumente für den aufzurufenden Delegaten.
  /// </param>
  /// <returns>
  /// Rückgabewert des letzten ausgefürten Delegaten oder null, wenn
  /// a) kein Delegat ausgeführt wurde oder
  /// b) der Delegat void zurückgibt
  /// </returns>
  public static object ExecuteBreakable(this Delegate del,
                                        BreakCallback callback,
                                        params object[] delArgs)
  {
    object val = null;
    if(del != null)
    {
      foreach(Delegate d in del.GetInvocationList())
      {
        if(d != null)
        {
          val = d.DynamicInvoke(delArgs);
          if((callback == null) && ( val == null)) break; 
          if((callback != null) && callback(del, d, val)) break;
        }
      }
    }
    return val;
  }
  /// <summary>
  /// ExecuteBreakable - Erweiterungsmethode für Delegaten.
  /// Diese Methode ermöglicht, einen Multicast-Delegaten sequenziell
  /// auszuführen und anhand des Rückgabewertes der einzelnen Delegaten
  /// in der Multicast-Liste den Multicast abzubrechen.
  /// </summary>
  /// <param name="del">
  /// Der aufrufende/aufzuführende (Multicast-)Delegat.
  /// </param>
  /// <param name="breakCheck">
  /// Ein Objekt zur Steuerung der Multicast-Ausführung.
  /// Der Rückgabewert jedes Delegaten in der Multicast-Liste wird mit
  /// diesem Objekt verglichen.
  /// Sind die Werte gleich, wird der Multicast abgebrochen.
  /// </param>
  /// <param name="args">
  /// Argumente für den aufzurufenden Delegaten.
  /// </param>
  /// <returns>
  /// Rückgabewert des letzten ausgefürten Delegaten oder null, wenn
  /// a) kein Delegat ausgeführt wurde oder
  /// b) der Delegat void zurückgibt
  /// </returns>
  public static object ExecuteBreakable(this Delegate del,
                                        object breakOn,
                                        params object[] delArgs)
  {
    BreakCallback cb = (m,e,eRet) => { return object.Equals(eRet, breakOn); };
    return ExecuteBreakable(del, cb, delArgs);
    return null;
  }

    //---------------------------------------------------------------------------
    // Überladungen für alle Varianten des generischen Func-Delegaten.
    // Die Methoden rufen die allgemeinen Methoden für Delegaten (oben) auf.
    // Liefert die allgemeine Methode null, wird ein default(TRet) zurückgegeben.
    //---------------------------------------------------------------------------
    public static TRet ExecuteBreakable<TRet>(this Func<TRet> f, BreakCallback cb)
    {
      object ret = ExecuteBreakable((Delegate)f, cb);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }
    public static TRet ExecuteBreakable<TRet>(this Func<TRet> f, TRet breakOn)
    {
      object ret = ExecuteBreakable((Delegate)f, breakOn);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }

    public static TRet ExecuteBreakable<T, TRet>
      (this Func<T, TRet> f, BreakCallback cb, T a)
    {
      object ret = ExecuteBreakable((Delegate)f, cb, a);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }
    public static TRet ExecuteBreakable<T, TRet>
      (this Func<T, TRet> f, TRet breakOn, T a)
    {
      object ret = ExecuteBreakable((Delegate)f, breakOn, a);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }

    public static TRet ExecuteBreakable<T1, T2, TRet>
      (this Func<T1, T2, TRet> f, BreakCallback cb, T1 a1, T2 a2)
    {
      object ret = ExecuteBreakable((Delegate)f, cb, a1, a2);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }
    public static TRet ExecuteBreakable<T1, T2, TRet>
      (this Func<T1, T2, TRet> f, TRet breakOn, T1 a1, T2 a2)
    {
      object ret = ExecuteBreakable((Delegate)f, breakOn, a1, a2);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }

    public static TRet ExecuteBreakable<T1, T2, T3, TRet>
      (this Func<T1, T2, T3, TRet> f, BreakCallback cb, T1 a1, T2 a2, T3 a3)
    {
      object ret = ExecuteBreakable((Delegate)f, a1, a2, a3);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }
    public static TRet ExecuteBreakable<T1, T2, T3, TRet>
      (this Func<T1, T2, T3, TRet> f, TRet breakOn, T1 a1, T2 a2, T3 a3)
    {
      object ret = ExecuteBreakable((Delegate)f, breakOn, a1, a2, a3);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }

    public static TRet ExecuteBreakablec<T1, T2, T3, T4, TRet>
      (this Func<T1, T2, T3, T4, TRet> f, BreakCallback cb, T1 a1, T2 a2, T3 a3, T4 a4)
    {
      object ret = ExecuteBreakable((Delegate)f, cb, a1, a2, a3, a4);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }
    public static TRet ExecuteBreakablec<T1, T2, T3, T4, TRet>
      (this Func<T1, T2, T3, T4, TRet> f, TRet breakOn, T1 a1, T2 a2, T3 a3, T4 a4)
    {
      object ret = ExecuteBreakable((Delegate)f, breakOn, a1, a2, a3, a4);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }
}

ANLEITUNG für Überladungen

Angenommen es liegt ein Delegat der folgenden Fom vor:

delegate TRet CustomDelegate(T1 a1, T2 a2, ..., Tn an);

dann kann man so typisierte ExecuteBreakable-Überladungen erstellen:

// Überladung mit BreakCallback
static TRet ExecuteBreakable(this CustomDelegate del,
                      BreakCallback cb, T1 a1, T2 a2, ..., Tn an)
{
  object ret = ExecuteBreakable((Delegate)del, cb, a1, a2, ..., an);
  if (ret == null) return default(TRet);
  return (TRet)ret;
}
// Überladung mit Vergleichswert
static TRet ExecuteBreakable(this CustomDelegate del,
                      TRet breakOn, T1 a1, T2 a2, ..., Tn an)
{
  object ret = ExecuteBreakable((Delegate)del, breakOn, a1, a2, ..., an);
  if (ret == null) return default(TRet);
  return (TRet)ret;
}

Beispielprogramm
zur Erläuterung der Nutzung


public class MulticastTest
{
  public delegate bool BooleanDelegate();
  public delegate string StringDelegate(string input);
  
  public static void Write(string method, string ret)
  {
    Console.WriteLine("\t{0}, Rückgabe: '{1}'", method, ret);
  }
    
  public static void Main()
  {
    BooleanDelegate boolDel;
    StringDelegate strDel;
    
    MulticastTest test = new MulticastTest();
    boolDel = () => { Write("BoolTest 1", "false"); return false; };
    boolDel += () => { Write("BoolTest 2", "true"); return true; };
    strDel = (input) => { Write("StringTest 1", input); return input; };
    strDel += (input) => { Write("StringTest 2", input); return input; };
    
    string callStr = "Aufruf {0}, Multicast bricht ab wenn Rückgabewert {1}";

    // MyBool1 und MyBool2 werden aufgerufen
    Console.WriteLine(callStr,"BooleanDelegate", "true ist.");
    Console.WriteLine();
    boolDel.ExecuteBreakable((m,d,o) => { return (bool)o; });
    Console.WriteLine(Environment.NewLine);
    
    // MyBool2 wird nicht mehr aufgerufen
    Console.WriteLine(callStr,"BooleanDelegate", "false ist");
    Console.WriteLine("** BoolTest 2 wird nicht ausgeführt **");
    Console.WriteLine();
    boolDel.ExecuteBreakable((m,d,o) => { return !(bool)o; });
    Console.WriteLine(Environment.NewLine);
    
    // MyString1 und MyString2 werden aufgerufen
    Console.WriteLine(callStr,"StringDelegate", "'break' enthält");
    Console.WriteLine();
    strDel.ExecuteBreakable((m,d,o) => { return ((string)o).Contains("break"); },
                             "this is a return value");
    Console.WriteLine(Environment.NewLine);
    
    // MyString2 wird nicht mehr aufgerufen
    Console.WriteLine(callStr, "StringDelegate", "'break' enthält");
    Console.WriteLine("** StringTest 2 wird nicht ausgeführt **");
    Console.WriteLine();
    strDel.ExecuteBreakable((m,d,o) => { return ((string)o).Contains("break"); },
                             "this is a breaking return value");
    Console.WriteLine(Environment.NewLine);
    
    Console.Write("End of Test. Press any key to continue.");
    Console.ReadKey(true);
  }
}

multicast, delgate, delegat, rückgabe, rückgabewert, returnvalue, retun value

Gruß, MarsStein

Edit: Prüfung (callback!=null) verschoben
Edit: Rechtschreibfehler im Titel korrigiert
Edit: Korrektur in Beschreibung: Bei "Gibt die Callback true zurück" stand falsch: "Gibt die Callback false zurück"
Edit: Überarbeitung der Kommentare, neue Funktion in Anlehnung an den Vorschlag von herbivore
Edit: Überladungen für Func-Delegaten auf Anregung von kleines_eichhoernchen, Anleitung für typisierte Überladungen

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo MarsStein,

es wäre schön, wenn es noch eine zweite Erweiterung gäbe, bei der bei Delegaten mit boolschen Rückgabewerten, kein zusätzlicher Callback nötig ist, sondern direkt der Rückgabewert des momentanen DynamicInvoke über den Abbruch entscheidet.

herbivore

MarsStein Themenstarter:in
3.170 Beiträge seit 2006
vor 13 Jahren

Hallo herbivore,

Einerseits habe ich das eigentlich für Delegaten geschrieben, die man nicht unbedingt selbst in der Hand hat, und die keine Kenntnis von der Logik der Erweiterungsmethode haben. Eine Methode wie Du sie vorschlägst, hat ja eigentlich nur Sinn wenn die Subscriber Kenntnis von dieser Logik haben, und entsprechend implementiert sind. Das war in meiner Anforderung für die ich die Methode geschrieben habe nicht der Fall, und ich wollte es auch entkoppelt haben.

Andererseits ist Dein Vorschlag als zweite Erweiterung ja durchaus sinnvoll, ich werde mal drüber nachdenken, wie ich das realisieren kann. Dann müsste ich das ganze ja auf Delegaten beschränken, die Boolean zurückgeben, und in der Parameterliste flexibel bleiben wie bisher.

Allerdings bin ich in den nächsten Tagen unterwegs (heut auch schon) und werde keine Gelegenheit zu programmieren und nur sehr wenig Zeit und Möglichkeit haben, hier mal reinzugucken - wird also vermutlich erst Freitag oder am WE was werden.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

MarsStein Themenstarter:in
3.170 Beiträge seit 2006
vor 13 Jahren

Hallo,

ich habe mir herbivores Vorschlag mal hergenommen und noch was gebastelt. Dieses schöne Stück Code ist dabei rausgekommen:

  public static object ExecuteBreakable(this Delegate del,
                                        object breakOn,
                                        params object[] delArgs)
  {
    if(del != null)
    {
      BreakCallback cb = (m,e,eRet) => { return object.Equals(eRet, breakOn); };
      return del.ExecuteBreakable(cb, delArgs);
    }
    return null;
  }

Damit ist es möglich, nicht nur bei Booleans, sonder bei allen Rückgabetypen direkt über den Rückgabewert einen Abbruch des Multicast zu erzwingen.
Der Abbruch erfolgt, wenn das in 'breakOn' übergebene Objekt dem Rückgabewert eines Delegaten entspricht.

@herbivore
Ein Aufruf mit true entspräche somit Deinem Vorschlag für Booleans. Aber so ist es noch flexibler 8)

Ich habe die neue Version mit überarbeiteten Kommentaren im Startbeitrag eingestellt

Gruß, MarsStein

**Edit: **Ein Aufruf mit null führt zwar zu der Überladung mit der BreakCallback, wird aber dort so behandelt wie ein Abbruchobjekt (Abbruch wenn ein Delegat aus der Multicastliste null zurückgibt)

Edit: Prüfung auf null in der neuen Funktion (kann nur vorkommen, wenn die Methode normal - nicht als Erweiterungsmethode - aufgerufen wird, aber man weiss ja nie...) - auch im Startbeitrag

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

3.971 Beiträge seit 2006
vor 13 Jahren

Das mit dem delArgs und dem Rückgabewert Object gefällt mir noch nicht so ganz.

Cool wäre wenn man wie bei Func<T, TRET> die Einzelnen Parameter nicht geboxt werden müssten

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

MarsStein Themenstarter:in
3.170 Beiträge seit 2006
vor 13 Jahren

Hallo kleine_eichhoernchen,

Du hast völlig recht. Allerdings ist das schwierig zu realisieren, ich müsste ja immer einen Delegatentypen vorgeben, um damit typisiert arbeiten zu können.

Für die bereits vorgegebenen Varianten von Func<> habe ich das aber mal gelöst:

    public static TRet ExecuteBreakable<T, TRet>
      (this Func<T, TRet> f, BreakCallback cb, T a)
    {
      object ret = ExecuteBreakable((Delegate)f, cb, a);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }
    public static TRet ExecuteBreakable<T, TRet>
      (this Func<T, TRet> f, TRet breakOn, T a)
    {
      object ret = ExecuteBreakable((Delegate)f, breakOn, a);
      if (ret == null) return default(TRet);
      return (TRet)ret;
    }

Ähnlich Überladungen für alle Varianten von Func<> habe ich im Startbeitrag hinzugefügt.
Außerdem habe ich dort auch eine Anleitung eingestellt, wie man für beliebige Delegaten typisierte Überladungen erstellen kann.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca