Laden...

Wie kann ich eine Liste mit Events erstellen?

Erstellt von GeneVorph vor 5 Jahren Letzter Beitrag vor 5 Jahren 1.355 Views
G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 5 Jahren
Wie kann ich eine Liste mit Events erstellen?

Hallo,

ich habe neulich als c#-Fingerübung ein kleines rundenbasiertes Spiel erstellt. Mein Spiel benötigt Basisregeln, die in einer eigenen Klasse abgearbeitet werden. Darüber hinaus gibt es Ergänzungsregeln, die ich möglichst losely coupled implementieren möchte.

Welche Regeln angewandt werden sollen, entscheidet der User per CheckBoxes.

Ich bin auf zwei Lösungsstrategien gekommen, von denen die erste auch funktioniert:

Strategie A: Ergänzungsregeln per Interface implementieren

Ich habe eine Klasse IRules, mit zwei Membern (vom Typ Player, genannt Player1, Player2) und einer Methodendeklaration void ApplyRule().

Sieht dann so aus:

public interface IRule
{
    public Player Player1 { get; set; }
    public Player Player2 { get; set; }

    void ApplyRule(Player1, Player2);
}

Alles was ich nun noch tun musste, um Ergänzungsregeln einzufügen, war eine Regel in Form einer Klasse zu erstellen, die IRule implementiert. Zu jeder Ergänzungsregel gibt es eine CheckBox, die einen bool true setzt, sofern die CheckBox angeklickt wurde. Ist dies der Fall wird beim Programmstart jede Regel, deren bool true ist in eine Liste vom Typ
List<IRule> AdditionalRules geschrieben. Anschließend wird über die Liste iteriert und zwar so:


if (AdditionalRules.Count() != 0)
{
    foreach (var rule in AdditionalRules)
    {
       rule.ApplyRule(Player1, Player2);
    }
}

Das funzt wunderbar 😃

Strategie B:
Jetzt zur eigentlichen Frage:
mir schien, ich könnte das Ganze evtl. etwas eleganter lösen, wenn ich statt des Interfaces auf Events zurückgreife. Konkret: eine Liste, in der Events enthalten sind, die dann in der foreach-Schleife per Invoke() aufgerufen werden. Mit folgendem Code kann ich bisher aufwarten:

In der die Ergänzungsregeln bearbeitenden Klasse:

List<EventHandler> RulesList { get; set; }

public void ApplyRules()
{
   foreach (var rule in RulesList)
   {
      rule?.Invoke(this, EventArgs.Empty);
   }

}

Nun der Problemteil:

In meiner Spiele-Klasse habe ich folgenden Code:

List<EventHandler> RulesList { get; set; }

public event EventHandler XtraRuleOne;

public Game()
{
  List<EventHandler> initList = new List <EventHandler>();

  RulesList = initLIst;
}

private void RegisterExtraRules()
{
    RulesList.Add(XtraRuleOne); // genau dieser Schritt funktioniert nicht*
}

*leider schreibe ich diesen Post nicht zu Hause, d.h. ich kann momentan nicht genau den Fehlerhinweis wiedergeben, aber VisualStudio motzt schon zur Compile Time. Ich liefere nach...

Lange Rede, kurzer Sinn: ist das überhaupt möglich? Events einer Liste hinzuzufügen? Und wenn ja: wie?

Vielen Dank,

Vorph

P.S.: Ich habe in meinem Beispiel auf den Standard-EventHandler zurückgegriffen, um die Beispiele etwas übersichtlicher zu halten. Tatsächlich bräuchte ich aber so etwas:

public delegate void XtraRuleEventHandler(Player Player1, Player Player2);
public event XtraRuleEventHandler OnXtraRuleApplying; <-- dieser soll in die Liste

T
461 Beiträge seit 2013
vor 5 Jahren

Hallo,

ich finde du hast mit der Schnittstelle schon die bessere Lösung gefunden und würde dabei bleiben.

Grüße

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

4.931 Beiträge seit 2008
vor 5 Jahren

Hallo GeneVorph,

bezogen auf dein "PS": die Typen der EventHandler müssen natürlich übereinstimmen, da beim Invoke ja die Anzahl und Typen der Parameter passen müssen.

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 5 Jahren

@Th69: Ja, da hast du natürlich Recht - im Projekt hatte ich das auch berücksichtigt, allerdings gelingt / gelang es mir nicht die einzelnen Events (XtraRuleOne, XtraRuleTwo...) in eine Liste zu adden. Worin siehst du die Vorteile der Interface-Lösung?

@ThomasE.: wahrscheinlich wird das auch so bleiben. Trotzdem täte mich interessieren, ob auch das mit den Events geht.

Apropos: ich hatte es gerade nochmal versucht - und der Fehler taucht nicht mehr auf? Strange - ich bin mir sicher, ich hatte es gestern Abend genau so gemacht, aber...

Ich bleibe am Ball und poste meine Lösung, sobald ich entweder den Fehler wieder generieren kann oder eine funktionierende Lösung mit Events habe. Einstweilen vielen Dank!

2.298 Beiträge seit 2010
vor 5 Jahren

Zuallererst bietet die Variante mit den Interfaces den großen Vorteil, dass du Regeln flexibler hinzufügen und entfernen kannst. Bei Events musst du immer wieder an der Game-Klasse rumwerkeln.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 5 Jahren

OK - manchmal ist es im Prinzip ganz einfach, man muss halt wissen, was genau man will 😃 Ich kann hier jedenfalls festhalten, dass je mehr ich in Richtung Events gegangen bin, desto mehr wurde mir bewusst, dass ich dafür immer wieder in der Games-Klasse rumschrauben musste. Danke, InFlames2k, dein Post beschreibt das Dilemma ganz gut 😉

Ich versuche mal zusammenzufassen:
Eigentlich ist es auch mit Events möglich, jedoch muss ich dann die behandelnden Klassen/Methoden in der Game-Klasse zusätzlich registrieren. Und instanziieren. Im Prinzip tut es in meinem Fall ein Multicast-Delegate:


public delegate void RuleEventHandler(Player Player1, Player Player2);

public event RuleEventHandler OnRuleOneActive;
public event RuleEventHandler OnRuleTwoActive;
public event RuleEventHandler OnRuleThreeActive;
.
.
.

Wie gesagt, in meinem Fall würde das reichen, weil die Regeln an einem zentralen Punkt implementiert werden können. Ich kann also alle Events in eine Liste geben, und zwar so:


List<RuleEventHandler> AdditionalRules = new List<RuleEventHandler>();

    if (AdditionalRuleOne)
     {
       AdditionalRules.Add(OnRuleOneActive);
     }

Später, bei der Anwendung der Regeln, iteriere ich in der die Regeln ausführenden Methode über die AdditionalRules-Liste:

{
    foreach( var rule in AdditionalRules)
    {
       rule?.Invoke(Player1, Player2);
    }
}

Soweit, so gut. Allerdings wird es - wahrscheinlich - eher so sein, dass eben NICHT an einer zentralen Stelle alle Regeln behandelt werden.

Dann wird es echt kompliziert - wie gesagt, der Event muss ja auch abonniert und eine die Regel behandelnde Klasse deklariert und initialisiert werden. Vergisst man da einen Schritt, wird es je nach Komplexität des Spiels spannend..

Tatsächlich bin ich jetzt auch an dem Punkt angelangt, wo sich zeigt, dass Interfaces in diesem Fall, hm, pflegeleichter (?) sind. Der Impact auf andere Klassen ist minimal, bis nicht vorhanden - und darüber hinaus ein Kinderspiel beim Erstellen (s. meinen ersten post).

Ich habe definitiv zu wenige Coder in meinem Umfeld, manchmal ist ein kurzer knapper (und verbal geführter) Austausch so viel zielführender als Google 😃
Und manchmal ist die erste Lösung halt einfach die richtige 😃

Vielen Dank für eure Beiträge!

EDIT: ich konnte den ursprünglichen Fehler nicht mehr reproduzieren; keine Ahnung wo und bei was ich da Mist genbaut hatte^^ Ich sollte nach 24 Uhr einfach nicht mehr programmieren...

16.807 Beiträge seit 2008
vor 5 Jahren

Evtl. verstehe ich es nicht (oder ich hab es überlesen?) aber kann das sein, dass Du hier "logische Spiele-Events" mit ".NET Events" versuchst gleich zu stellen?

Eine Aktion oder ein Ereignis in einem Spiel kann ja eine ganz normale Klasse sein, die mit einem .NET Event (wie einem MouseOver) nichts zutun hat.
Oder sprichst Du hier wirklich von Events im Sinne der .NET Technologie? Oder "missbrauchst" Du die EventHandler Klasse von .NET für Spiele-Logik? 🤔

5.941 Beiträge seit 2005
vor 5 Jahren

Hallo GeneVorph

Ich würde auch zur Interface-Implementation tendieren. Auch denke ich, dass Abt richtig erkannt hat, dass du wohl .NET Events nutzt, wo sie nicht vorgesehen sind und auch keinen Sinn machen.

Du kannst unterscheiden zwischen Interface- / Klassenbasierter Implementierung und Implementierungen mit Funktions-Zeigern (Delegates). Dass ist das was du wohl gesucht hast. Die .NET Events nutzen auch Delegates bspw. den EventHandler oder EventHandler<T>-Typ. Diese sind allerdings dazu da "richtige" Event-Szenarien generisch abzudecken. Was du hier hast, ist eine Spezifizierung einer Schnittstelle die einen Player1 und 2 kennt, sowohl ein Stück Code hat, dass damit ausgeführt werden soll und die Player-Instanzen entsprechend den Regeln verändert.

Du sagst ja: Player1 und Player2 rein und Customcode (void ApplyRule) ausführen, der die Regel beinhaltet und die Player-Instanzen benötigst. Das kann auch mit Instanzen von Funktioneszeigern (Delegaten) umgesetzt werden. Dort sieht das "Interface" so aus: ApplyRule(Player player1, Player player2). Also Players rein und beim ausführen der Funktionsinstanz, Regel anwenden (Die Regel selber, als Funktionsbody implementiert). In beiden Fällen kannst du die Regel sowie die Spieler zuweisen und zu einem gewünschten Zeitpunkt, die Regel(n) anwenden. Das muss nicht gleich bei der Instanziierung passieren.

ps: Action<Player, Player> ist nichts anderes als ein delegate-Typ der X generische Argumente entgegennimmt. Definiert eine Instanz einer Funktion (so kann man sich das vorstellen), die so aussieht: void(player, player) { body }.

Action<T>, Func<T> ist ein interessantes Gebiet, lernreich und kann dann und wann eingesetzt werden.

Hier ein wenig Beispielcode dazu:


using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Player player1 = new Player();
            Player player2 = new Player();

            // Interface Implementierung
            List<IRule> rules = new List<IRule>();
            rules.Add(new HandycapRule(5, 0));

            foreach (IRule rule in rules)
            {
                rule.Player1 = player1;
                rule.Player2 = player2;

                // Hier oder zu einem anderne Zeitpunkt Regel anwenden.
                rule.ApplyRule();
            }

            // Funktions-Implementierung (Keine Events nötig, nur Delegates)
            List<Action<Player, Player>> rulesActions = new List<Action<Player, Player>>();

            rulesActions.Add((p1, p2) => p1.Strength -= 5);
            // ...

            // Hier oder zu einem anderen Zeitpunkt wo Player1, Player2 verfügbar sind.
            foreach (var ruleAction in rulesActions)
            {
                ruleAction(player1, player2);
            }

            // Oder
            rulesActions.ForEach(rule => rule(player1, player2));
        }
    }

    public class Player
    {
        public Player()
        {
            this.Strength = 10;
        }

        public string Name { get; set; }
        public int Strength { get; set; }
    }

    // Interface Implementierung

    public interface IRule
    {
        Player Player1 { get; set; }
        Player Player2 { get; set; }

        void ApplyRule();
    }

    // Ginge auch, ist näher an Funktionsimplementierung
    public interface IRuleFunction
    {
        void ApplyRule(Player player1, Player player2);
    }

    public class HandycapRule : IRule
    {
        private int _player1HandycapAmount;
        private int _player2HandycapAmount;

        public HandycapRule(int player1HandycapAmount, int player2HandycapAmount)
        {
            this._player1HandycapAmount = player1HandycapAmount;
            this._player2HandycapAmount = player2HandycapAmount;
        }

        public Player Player1 { get; set; }
        public Player Player2 { get; set; }

        public void ApplyRule()
        {
            this.Player1.Strength -= this._player1HandycapAmount;
            this.Player2.Strength -= this._player2HandycapAmount;
        }
    }
}

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011