Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

1

01.04.2016, 16:02

[C#] Event Based Components (Früher: Casten einer ObservableCollection bzw. deren Elemente)

Moin Leute,

heute mal nichts mit MVVM, sondern ein reines C# Problem. Ich poste mal direkt den gekürzten Code:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface IInputPin
{
    // Eigenschaften
    ObservableCollection<IOutputPin> Inputs { get; }
}
public class InputPin<T> : IInputPin where T : Value, new()
{
    // Eigenschaften
    public ObservableCollection<OutputPin<T>> Inputs { get; private set; }
    
    ObservableCollection<IOutputPin> IInputPin.Inputs
    {
        get
        {
            return Inputs as ObservableCollection<IOutputPin>;
        }
    }
}
Kurze Erklärung: Ich habe eine Generic-Klasse InputPin<T>, die eine Eigenschaft Inputs besitzt, die den Generic-Parameter in seinem Typ benutzt. Wenn ich nun allgemein alle InputPins in einer Auflistung habe, dann benutze ich das IInputPin Interface. Über das muss ich auch auf die Eigenschaft Inputs mit C# zugreifen können. Grund: CollectionChanged muss registriert werden. Gibt es eine Möglichkeit das zu realisieren? Dass der Ansatz oben eine NullPointerException beim Typecast wirft ist mir klar - ich muss ja die Elemente des Arrays casten und nicht das Array selbst. Das kann ich auch mit Linq.Cast<>() machen, allerdings habe ich dann keine ObservableCollection mehr, die eine Änderung an der original ObservableCollection liefert. :(

Einzige Möglichkeit, die mir einfiele, wäre zwei Auflistungen parallel zu verwalten und dann über Code in der Generic-Klasse zu syncronisieren, aber das klingt mir zu kompliziert für dieses Problem.

Hoffe mir kann jemand helfen!

EDIT:
Folgenden Code habe ich vergessen zu posten:

C#-Quelltext

1
2
interface IOutputPin { }
public class OutputPin<T> : IOutputPin where T : Value, new() { }
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von »CeDoMain« (02.04.2016, 22:00) aus folgendem Grund: Überschrift auf was Sinnvolles geändert...


LInsoDeTeh

Treue Seele

Beiträge: 372

Wohnort: Essen, Deutschland

Beruf: Team Lead Inhouse-Entwicklung

  • Private Nachricht senden

2

01.04.2016, 16:32

Ich hoffe, ich habe die Frage richtig verstanden...
Du kannst auch das Interface bereits generisch gestalten, also etwas in der Art:

Quellcode

1
2
3
interface IInputPin<T> where T: IOutputPin {
  ObservableCollection<T> Inputs { get; }
}


Damit könnte deine Klasse generisch so aussehen:

Quellcode

1
2
3
4
class InputPin<T> : IInputPin<T> where T: IOutputPin {
  public  ObservableCollection<T> Inputs { get; private set; }

}


und dann bastelst du einfach eine

Quellcode

1
InputPin<OutputPin<S>> where S : Value, new()

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

3

02.04.2016, 01:10

Sorry, aber ich weiß nicht, wie mir das helfen soll. :( Ein generisches Interface bringt mir nichts, denn dann kann ich die InputPins nicht mehr alle (unabhängig vom Generic) in ein Array packen.

Ich habe im ersten Post noch was hinzugefügt, das erklärt vielleicht, warum ich die Collection meine Casten zu können.
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

4

02.04.2016, 03:59

Was willst du mit dem Code erreichen? Was würden die Interfaces bei dir definieren? Um was würden diese erweitert werden? Welche anderen Implementierungen der Interfaces schweben dir vor? Was würde als Typparameter verwendet werden?
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

5

02.04.2016, 17:02

Ich hoffe mal, die Frage von Sacaldur ist an mich gerichtet... ich hole mal ein bisschen aus und erkläre den Sinn dieser Klassen bzw. Interfaces:

In meinem Programm kann der Anwender verschiedene Objekte miteinander verknüpfen, damit diese (die Objekte) Werte austauschen. Ein Objekt kann dazu Schnittstellen zur Verfügung stellen - das sind die sogenannten Pins. Es gibt Input- und OutputPins. Dabei wird immer nur vom OutputPin zum InputPin übertragen. InputPins können von beliebig vielen OutputPins einen Wert empfangen, die Werte werden dann abhängig vom Werttyp gemischt. Genauso können OutputPins an mehrere InputPins einen Wert versenden - der wird dann einfach kopiert und allen geschickt.

Ein OutputPin kennt alle seine verknüpften InputPins und andersherum. Wenn ein OutputPin einen Wert in seinen InputPins setzen möchte, dann geht er seine intere Pinliste durch und setzt in diesen Pins überall die Value-Eigenschaft neu. Die Pinklassen sind generisch, damit nur Pins verknüpft werden können, die auch den selben Typ besitzen. Daher die Generic-Klassen in meinem Code oben mit den ObservableCollections als Eigenschaft.

Das Werte Austauschen und Mischen an sich funktioniert wunderbar, nur mit dem Verknüpfen habe ich jetzt Probleme. Zum Testen habe die Pins per Code verknüpft, das war kein Problem, weil ich immer genau wusste was für ein Werttyp das gerade ist. Wenn ich das nun den User machen lassen möchte, brauche ich eine GUI, die mit allen Werttypen klarkommt!

Ich beschreibe mal wie meine bisherige Verknüpfungs-GUI funktioniert: Es wird ein Pin ausgewählt, dann auf "verknüpfen mit..." geklickt und dann öffnet sich ein neues Fenster - die Verknüpfungs-GUI. Diese bekommt den Pin mitgeteilt für den sie aufgerufen wird - da tritt dann das erste Problem auf: Ich habe zwar zwei GUIs - eine für InputPins und eine für OutputPins, aber was mache ich mit den Werttypen - da kann ich unmöglich für alle 20 verschiedenen Werttypen eine eigene GUI schreiben?! Darum habe ich mir das Interface IInputPin bzw. IOutputPin gebaut, damit die GUI eine allgemeine nicht-Generic-Schnittstelle hat, um Pins übergeben zu bekommen.

Hat die GUI einen Pin bekommen, findet sie über das IInputPin bzw. IOutputPin Interface heraus, welchen Typ der GenericParameter (Werttyp) hat und zeigt dann alle Pins an, die auch diesen Typ haben und somit verbunden werden können bzw. schon verbunden sind. Der Benutzer kann dann mit Mausklick die Verknüpfungen ändern und die GUI setzt über die Eigenschaften Inputs bzw. Outputs der Interfaces IInputPin und IOutputPin die verknüpften Pins. Daher die ObservableCollections in den Interfaces oben im Code.

Jetzt hoffe ich mal, dass da einer durchsteigt! Ich hab mir Mühe gegeben dass verständlich aufzuschreiben. ;)

Wenns eine Möglichkeit gibt, dass ich um die Interfaces herumkomme und der GUI eine Generische klasse gebe, dann finde ich das auch gut! Aber bisher habe ich es nicht geschafft WPF von Generic-Klassen zu überzeugen. Damit meine ich sowas:

C#-Quelltext

1
public partial class InputPinConnectDialog<T> : UserControl { }
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

6

02.04.2016, 17:47

Ja, die Frage war auch an dich gerichtet. ;)
Die Beschreibung erinnert mich sehr an Event Based Components, vielleicht kannst du dir von denen auch noch ein wenig was abschauen (auch wenn nicht unbedingt für das aktuelle Problem).

Ich denke mal, dass das von dir geschriebene Control nicht unbedingt über einen Typparameter verfügen muss, der mit dem der Pins übereinstimmt, die damit gehandhabt werden. Stattdessen würde vielleicht auch ein Wert (Zeichenkette, Type, ...) zur Unterscheidung reichen. Die Commands würden dann darauf Rücksicht nehmen, wenn Filterungen durchgeführt (nur passende Pins) werden müssen.
Weiterhin wäre relevant zu wissen, wie die Pins in dem "Container" (also dem Objekt, welches die Pins besitzt) verwaltet werden. Gibt es für diese mehrere Member oder haben diese auch nur Listen von Pins? Sollten es Member (Variablen oder Properties) sein, müssten die vom UI aus über ihre Namen angesprochen werden (ggf. zwischenzeitlich als Zeichenketten), weshalb dieses selbstgeschriebene Control wohl nur mit Zeichenketten hantieren müsste. (Die Commands für das Aufbauen von Verknüpfungen würden diese dann wieder auswerten.)

Welche Typparameter wirst du denn unbedingt brauchen? Ist es eine so große Zahl, dass Generik unbedingt notwendig ist?
Grundsätzlich ist es zwar besser, mit Generik an dieser Stelle zu arbeiten, allerdings könnte man die Probleme evtl. auch umgehen. Wenn bspw. nur 1 einziger Typparameter effektiv verwendet wir, würde Generik gänzlich überflüssig sein. Wären es nur ein paar wenige, könnte man für jeden Typparameter eine konkrete Implementierung haben (bspw. Int32InputPin und StringInputPin, beide mit InputPin<T> als Basisklasse).

Ich denke, letztendlich dürfte es wohl auf eine Variante hinauslaufen, bei der das UI nur mit Namen und Typen (als Strings) umgeht, nicht aber mit den Pins selbst.
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

7

02.04.2016, 21:55

Na das ist ja mal ein interessanter Link! Ich hab mir das mit den Pins komplett selbst ausgedacht auch zuerst mit Events - nur wollte ich denen aus Performancegründen nicht vertrauen. Allerdings glaube ich, sollte ich nicht ständig auf Performance achten - da hab ich später noch ganz andere Schrauben wo ich drehen kann. ;) Naja ist ja schön, wenn andere dieselbe Idee haben! Und auf der letzten Seite wird auch auf das Verknüpfen durch grafische Editoren eingegangen ... ich denke er sieht da eher die Visualisierung als Problem, aber es stimmt schon was da gesagt wird! Schonmal vielen Dank für den Link! :)

Ich werde also die Methodenaufrufe bzw. die direkte Manipulation der Eigenschaften durch generische Events ersetzen.

Ich denke mal, dass das von dir geschriebene Control nicht unbedingt über einen Typparameter verfügen muss, der mit dem der Pins übereinstimmt, die damit gehandhabt werden. Stattdessen würde vielleicht auch ein Wert (Zeichenkette, Type, ...) zur Unterscheidung reichen. Die Commands würden dann darauf Rücksicht nehmen, wenn Filterungen durchgeführt (nur passende Pins) werden müssen.
Das habe ich auch so gelöst: Das IInputPin Interface hat einen Member GenericName, der dann von der GenericKlasse festgelegt wird. Damit wird dann auch letztendlich gefiltert.

Aber wie übergebe ich den Pin an sich an den Dialog bzw. wie soll der Dialog die Verknüpfung vornehmen? Kann er doch garnicht so direkt, weil er den wirklichen Werttypen nicht kennt, sondern nur das Interface. Sollte der Dialog vielleicht nur mit IDs (die jedem Pin zugewiesen werden) angeben, welcher Pin mit wem verknüpft wird und diese Liste dann an den Pin weitergegeben (oder ein ViewModel), dass dann die IDs in Objekte wandelt und diese deren Events verknüpft?

Welche und wie viele Pins ein Container besitzt ist über verschiedene Interfaces definiert, die von den Komponenten implementiert werden können. Der Plan ist nämlich, dass das System beliebig um Komponenten erweiterbar ist. Das gleiche gilt auch für die Werte. Bis jetzt habe ich 2 Werte implementiert (Farbe und Helligkeit) aber ich habe mir schon den Code für fast 20 andere Typen ausgedacht... Generic ist also definitiv nötig, ich hab keinen Bock auf einen Wald von irgendwelchen Klassen, nur weil ich keine Generics verwende. ;(

EDIT: Habe den Titel mal erweitert, weil ja jetzt um ein Designproblem geht. ^^
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

8

04.04.2016, 11:59

Ich pushe das Thema mal! Gibt es keinen, der eine Idee hat, wie man Generic-Klassen an eine nicht Generic-Klasse übergeben kann? Gibt es das Problem nicht immer bei Generics oder Templates? Was mache ich, wenn ich kein Interface verwenden möchte?
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

LInsoDeTeh

Treue Seele

Beiträge: 372

Wohnort: Essen, Deutschland

Beruf: Team Lead Inhouse-Entwicklung

  • Private Nachricht senden

9

04.04.2016, 14:49

Ich habe damit mal ein bisschen rumprobiert, eventuell kann man das ja so in diese Richtung lösen:

Als erstes habe ich ein Interface

C#-Quelltext

1
2
3
4
public interface IPin
        {
            Type ValueType { get; }
        }

Das macht noch keinen Unterschied zwischen Input und Output Pin, es stellt lediglich eine Eigenschaft zur Verfügung, die den Typ des übergebenen Parameters enthält.
Das gleiche gilt für die abstrakte Basisklasse, die das Interface implementiert und als erste das Generikum einführt:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
public abstract class Pin<TValueType> : IPin
            where TValueType : Value, new()
        {
            public Type ValueType { get; private set; }

            protected Pin()
            {
                ValueType = typeof(TValueType);
            }
        }

Damit hast du schon einmal einen Basispin, der den Typ seines Generikums rausschmeißen kann. Jetzt können deine Input- und Outputpins die abstrakte Klasse konkret implementieren:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class InputPin<TValueType> : Pin<TValueType>
            where TValueType : Value, new()
        {
            public ObservableCollection<OutputPin<TValueType>> Outputs { get; private set; }

            public InputPin()
            {
                this.Outputs.CollectionChanged += (sender, args) => { /* whatever */ };
            }
        }
        public class OutputPin<TValueType> : Pin<TValueType>
            where TValueType : Value, new()
        {
            public ObservableCollection<InputPin<TValueType>> Inputs { get; private set; }

            public OutputPin()
            {
                this.Inputs.CollectionChanged += (sender, args) => { /* whatever */ };
            }
        }


Wobei man jetzt noch überlegen kann, ob man lediglich für die Bezeichnung Inputs/Outputs wirklich fast 2x die gleiche Klasse notwendig ist, oder ob man nicht stattdessen einen "ConcretePin" implementiert, der sowas wie "Subscribers" statt Inputs/Outputs hat, aber das hängt von deinen Anforderungen ab.

Auf diese Weise kannst du jedenfalls konkrete Eventhandler implementieren (habe ich jetzt schon mal testweise oben im Konstruktor gemacht, geht aber auch außerhalb), und du kannst das ganze in ein Array IPin[] packen, wo jedes Element eine Property "ValueType" hat, die dir den Typ ihres Inhalts zurückgibt.

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

10

04.04.2016, 19:25

Hey Danke, dass du dich damit beschäftigst! :) So wie du es vorgeschlagen hast, läuft es ja bisher. Dabei habe ich aber das Problem, dass ich über das Interface nicht auf die ObservableCollections zugreifen kann, denn die haben ja einen Generic-Typ. Und casten klappt nicht - zumindest nicht so, dass nachher noch CollectionChanged zugreifbar ist.

Wahrscheinlich muss ich doch ein neues Protokoll bauen, mit dem die ConnectGUI den Pins sagt mit wem sie sich verbinden sollen. :(
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »CeDoMain« (10.05.2016, 23:34)


Werbeanzeige