Autor Beitrag
Scari
Hält's aus hier
Beiträge: 6



BeitragVerfasst: So 04.12.11 17:47 
Hallo,

ich beschäftige mich gerade mit Generics, bzw. komme in meinem XNA Spiel nicht weiter. Ich hoffe ihr könnt mir weiterhelfen.
Ich hab den Code mal auf ein anschaulicheres Beispiel übertragen:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
    public interface Auto<T> 
    {
        void fahren(T l);
    }

    public class Benz : Auto<Diesel>
    {
        public void fahren(Diesel l)
        {
            
        }
    }

    public class Ferarri : Auto<Benzin>
    {
        public void fahren(Benzin l)
        {

        }
    }


Nun möchte ich die Auto Klasse als Attribut in einer Klasse deklarieren und in dem Konstruktor das Objekt entweder als Benz oder als Ferarri deklarieren:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
class Program
{
  public Auto<Diesel> auto;
  
  public Program(bool test)
  {
    if (test)
    {
      auto = new Benz();   //Funktioniert
    }
    else
    {
      auto = new Ferarri(); //Funktioniert nicht, da ja Diesel als Datentyp angegeben wurde
    }
  }
  
}


Das funktioniert jedoch nicht, da ich schon gezwungen bin ein Datentyp bei der Deklaration anzugeben. Wie mach ich das so, dass das funktioniert? Vielen Dank schon mal.

Gruß
Scari
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4805
Erhaltene Danke: 1061

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: So 04.12.11 18:26 
Hallo und :welcome:

du könntest eine (nichtgenerische) Basisschnittstelle für deine generische Auto<T>-Schnittstelle anbieten:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
public interface IAuto
{
}

public interface IAuto<T> : IAuto // Interfaces sollten immer mit I anfangen, d.h. IAuto<T>
{
    void fahren(T l);
}

Und benutzen würdest du dann nur:
ausblenden C#-Quelltext
1:
public IAuto auto;					

Da die Basisschnittstelle bisher keine Methoden aufweist, könntest du damit bisher aber auch nur die Variable 'auto' initialisieren, jedoch müßtest du zum Zugriff jeweils darauf 'casten' (was nicht sehr elegant ist).

Vllt. solltest du doch besser dein eigentliches Problem bei deinem XNA-Spiel beschreiben (denn dieses Auto-Beispiel halte ich nicht für so gelungen ;-))
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: So 04.12.11 18:35 
Das einfach zum funktionieren zu bringen wird nicht gehen da es schon logisch ein Problem enthält.

Stell dir vor du könntest die Definition der auto Instanzvariablen so gestalten das dein folgender Code kompiliert. Wie soll dann zur Compilezeit entschieden werden welcher potenzielle Aufruf der fahren Methode gültig ist? Der mit Benzin, Diesel oder einer mit zum Beispiel String? Wie du dir jetzt vorstellen können solltest geht das nicht. Um das zur Compilezeit zu können benötigt es schon das Wissen über den konkreten generischen Typ (oder einen entsprechenden Constraint). So wie das jetzt von dir vorgesehen ist sehe ich nur den Weg den generischen Parameter der Klasse in einen generischen Aufruf der fahren Methode zu ändern damit du etwas hast an dem du gezielt im Code festlegen kannst ob jetzt ein Aufruf mit Benzin,Diesel etc. erfolgt. Ob das im echten Context jenseits dieses Beispiels sinnvoll kann man aber ohne den echten Kontext nicht wirklich entscheiden. Ansonsten ist es in diesem Beispiel mit Autos genauso (in einer vorzustellenden funktionierenden Version) wie im wahren leben. Ob du das richtige getankt hast weißt du eigentlich erst dann wenn du von der Tankstelle runter fährst(zur Laufzeit) und nicht schon während des Tankvorgangs (zur Tankzeit bzw. Compilezeit ;) ).
Scari Threadstarter
Hält's aus hier
Beiträge: 6



BeitragVerfasst: So 04.12.11 20:16 
danke für die Antworten. Ja das Beispiel ist doch nicht so toll, ich zeig euch mal das Problem anhand des Spiels.

Also ich habe ein Interface namens IInpunt:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
    interface IInput<T>
    {
        void UpdateState();
        void UpdatelastState();

        bool IsUp();
        bool IsDown();
        T getState();
        ..... usw.
     }


und 2 abgeleitete Klassen die entweder als Objekt das KeyboardState oder das GamepadState haben.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
    class Input_PC : IInput<KeyboardState>
    {
      
    }

    class Input_Xbox : IInput<GamePadState>
    {

    }


In meiner Spieler Klasse soll dann unterschieden werden ob die Steuerung über ein Gamepad oder eben über die Tastatur läuft.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
public class Player
{
public IInpunt<GamepadState> input {get; set;} 

        public Player(ContentManager content, SpriteBatch spriteBatch, Vector2 pos, bool pc)
        {  
            if (!pc)
            {
                 this.input = new Input_Xbox();
            }
            else
            {
                this.input = new Input_PC();
            }
        }

  //BeispielMethode
  public void Update()
  {  
      input.UpdateState();
  }
}


Würde ich eine Interface Klasse als Basis nehmen, wie TH69 beschrieben hat, müsste ich erst überall prüfen was für ein Typ das Objekt hat und dann entsprechend casten. Das wäre dann enorm viel aufwand. So dachte ich eben, dass ich einfach das input Objekt nehme und die Methoden aufrufe die ich brauche und so flexibel bin, da ich vor einfach nur als Input_Pc oder Input_Xbox instanziieren muss. Wie sollte ich das dann am besten machen?


user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Das einfach zum funktionieren zu bringen wird nicht gehen da es schon logisch ein Problem enthält.

Stell dir vor du könntest die Definition der auto Instanzvariablen so gestalten das dein folgender Code kompiliert. Wie soll dann zur Compilezeit entschieden werden welcher potenzielle Aufruf der fahren Methode gültig ist? Der mit Benzin, Diesel oder einer mit zum Beispiel String? Wie du dir jetzt vorstellen können solltest geht das nicht. Um das zur Compilezeit zu können benötigt es schon das Wissen über den konkreten generischen Typ (oder einen entsprechenden Constraint). So wie das jetzt von dir vorgesehen ist sehe ich nur den Weg den generischen Parameter der Klasse in einen generischen Aufruf der fahren Methode zu ändern damit du etwas hast an dem du gezielt im Code festlegen kannst ob jetzt ein Aufruf mit Benzin,Diesel etc. erfolgt. Ob das im echten Context jenseits dieses Beispiels sinnvoll kann man aber ohne den echten Kontext nicht wirklich entscheiden. Ansonsten ist es in diesem Beispiel mit Autos genauso (in einer vorzustellenden funktionierenden Version) wie im wahren leben. Ob du das richtige getankt hast weißt du eigentlich erst dann wenn du von der Tankstelle runter fährst(zur Laufzeit) und nicht schon während des Tankvorgangs (zur Tankzeit bzw. Compilezeit ;) ).


Constraints hab ich bis jetzt nicht wirklich verstanden. Wie meinst du das genau mit der generischen Methode? Kannst du mir da ein Codebeispiel geben?
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4805
Erhaltene Danke: 1061

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: So 04.12.11 20:46 
Hallo Scari,

du könntest also analog zum Auto-Beispiel eine nicht-generische Klasse IInput erzeugen, wo du dann alle Methoden (und/oder Eigenschaften) unterbringst, welche nicht von T abhängen.

Und alternativ bzw. zusätzlich könntest du noch eine Basisklasse bzw. Schnittstelle für deine States (KeyboardState, GamePadState, ...) anlegen und diese dann als Einschränkung (Constraint) für deine Schnittstelle (bzw. Klasse) anlegen:
ausblenden C#-Quelltext
1:
2:
3:
4:
interface IInput<T> where T : StateBase
{
  // ...
}

Dies macht aber auch nur dann Sinn, sofern du wiederum gleiche Methoden (und/oder Eigenschaften) in den State-Klassen hast, welche du dann als virtuell implementierst (damit du auf diese dann ohne 'casten' zugreifen kannst).
Ein Aufruf könnte dann z.B. so aussehen:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
public IInput<StateBase> input {get; set;}

// in einer Methode
StateBase state = input.GetState();
state.DoSomething(); // virtuelle Methode (bzw. abstrakt in der Basisklasse)


Evtl. wäre jedoch die Umstellung auf eine abstrakte Basisklasse InputBase (anstatt eines Interfaces) hier angebrachter, da du ja mit konkreten Instanziierungen (Input_PC bzw. Input_XBox) hier arbeiten möchtest.
Scari Threadstarter
Hält's aus hier
Beiträge: 6



BeitragVerfasst: So 04.12.11 22:19 
user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Hallo Scari,

du könntest also analog zum Auto-Beispiel eine nicht-generische Klasse IInput erzeugen, wo du dann alle Methoden (und/oder Eigenschaften) unterbringst, welche nicht von T abhängen.

Und alternativ bzw. zusätzlich könntest du noch eine Basisklasse bzw. Schnittstelle für deine States (KeyboardState, GamePadState, ...) anlegen und diese dann als Einschränkung (Constraint) für deine Schnittstelle (bzw. Klasse) anlegen:
ausblenden C#-Quelltext
1:
2:
3:
4:
interface IInput<T> where T : StateBase
{
  // ...
}

Dies macht aber auch nur dann Sinn, sofern du wiederum gleiche Methoden (und/oder Eigenschaften) in den State-Klassen hast, welche du dann als virtuell implementierst (damit du auf diese dann ohne 'casten' zugreifen kannst).
Ein Aufruf könnte dann z.B. so aussehen:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
public IInput<StateBase> input {get; set;}

// in einer Methode
StateBase state = input.GetState();
state.DoSomething(); // virtuelle Methode (bzw. abstrakt in der Basisklasse)


Evtl. wäre jedoch die Umstellung auf eine abstrakte Basisklasse InputBase (anstatt eines Interfaces) hier angebrachter, da du ja mit konkreten Instanziierungen (Input_PC bzw. Input_XBox) hier arbeiten möchtest.


danke für die Antwort. Soweit hab ich das verstanden, ich mach ein Input Interface ohne irgendwelche Generics:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
    public interface IInput1
    {
        void UpdateState();
        bool IsUp();
        //... usw.
    }


dann das 2. Interface mit allen Methoden die ein GamePadState bzw. KeyboardState brauchen.
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
    public interface IInput2<T> where T : StateBase
    {
        T getState();
  // ... usw.
    }


Ich hab das jedoch nicht genau verstanden, wie du das mit dem StateBase meinst. Wie soll denn diese Klasse bzw. Schnittstelle aussehen? Und von was sollen jetzt die Klassen Input_Pc und Input_Xbox ableiten, bzw. sollen die beiden IInput Interfaces den gleichen Namen haben?
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: So 04.12.11 22:34 
Zitat:
Constraints hab ich bis jetzt nicht wirklich verstanden. Wie meinst du das genau mit der generischen Methode? Kannst du mir da ein Codebeispiel geben?


Ich hatte codemäßig an folgende Änderung gedacht

ausblenden C#-Quelltext
1:
2:
3:
4:
public interface Auto<TTyp> where TTyp : IBrennstoff;
{
    void fahren<T>(T l) where T : IBrennstoff;
}


Wobei Diesel und Benzin beide IBrennstoff implementieren. Damit wäre das Problem gelöst das deine fahren Methode vom Übergabetypen her nicht eindeutig ist. So wie vorgeschlagen kannst du jetzt beliebigen IBrennstoff übergeben und musst dann halt zur Laufzeit prüfen ob der zum Fahrzeug passt. Denke aber nicht das dir das in deinem konkreten Problem weiterhelfen würde.

Ich kenne XNA nicht wirklich aber es sieht so aus als wäre es für einen generischen Ansatz schlecht geeignet. KeyboardState und GamePadState haben zum Beispiel keine gemeinsame Basis. Wenn du eine echte generische Oberfläche für deine Klassen möchtest musst du dich wahrscheinlich davon verabschieden die vorhandenen Typen in dieser Oberfläche wieder zu verwenden sondern eigene von diesen abstrahierte Typen verwenden die einfach besser geeignet wären (wie zum Beispiel abstrahierte Klassen für Keyboard, Gamepad etc. States die von einer gemeinsamen Basis ableiten wie von user profile iconTh69 vorgeschlagen). Die vorhandenen Typen würdest du dann nur noch Klassenintern verwenden um mit XNA zu sprechen und in der Oberfläche halt auf deinen eigenen Typen mappen. Ob es denn Aufwand wert ist musst du selbst entscheiden. Frameworks folgenden meist schon irgendeinem Paradigma dem man sich am besten anpasst. Ein anderes drüber zu legen sollte wohl überlegt sein ob das nicht mehr Probleme schafft als löst.
Yogu
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2598
Erhaltene Danke: 156

Ubuntu 13.04, Win 7
C# (VS 2013)
BeitragVerfasst: Di 06.12.11 21:48 
Überleg dir, welche Informationen du vom Eingabegerät wirklich brauchst, nicht, welche verfügbar sind. So funktionieren Interfaces.

Was genau das ist, kommt auf dein Spiel an. Wenn sich der Spieler z.B. im zweidimensionalen Raum bewegen können soll, gibt es zwei Möglichkeiten, wie du ihn steuern kannst:

  • Per Pfeiltasten: Links, Rechts, Hoch, Runter. Diese Tasten können entweder gedrückt sein, oder eben nicht.
  • Per Joystick (ThumbStick): Diesen Knopf kann man in zwei Richtungen (vorne/hinten, links/rechts) bewegen, außerdem unterschiedlich stark drücken. Also kann sich der Spieler in diesem Fall unterschiedlich schnell bewegen.

Um dies nun auf einen gemeinsamen Nenner zu bringen, kannst du ein Interface deklarieren, das zwei Zahlen angibt, und zwar je im Bereich von -1 bis 1 für die beiden Achsen. Der Joystick kann dieses Interface direkt beliefern, da es ja seinem Aufbau entspricht. Die Keyboard-Implementierung kann keine Zwischenschritte anzeigen, also gibt sie für die Eigenschaften einfach immer nur -1, 0 oder 1 zurück.

So habe ich ein Jump'n'Run-Spiel für Android und Desktop programmiert. Der gemeinsame Code stellt Schnittstellen bereit, die genau das beinhalten, was das Spiel wirklich braucht, und die Implementierungen für die beiden Plattformen stellen diese Informationen bzw. Methoden bereit.
Scari Threadstarter
Hält's aus hier
Beiträge: 6



BeitragVerfasst: Di 06.12.11 22:36 
user profile iconYogu hat folgendes geschrieben Zum zitierten Posting springen:
Überleg dir, welche Informationen du vom Eingabegerät wirklich brauchst, nicht, welche verfügbar sind. So funktionieren Interfaces.

Was genau das ist, kommt auf dein Spiel an. Wenn sich der Spieler z.B. im zweidimensionalen Raum bewegen können soll, gibt es zwei Möglichkeiten, wie du ihn steuern kannst:

  • Per Pfeiltasten: Links, Rechts, Hoch, Runter. Diese Tasten können entweder gedrückt sein, oder eben nicht.
  • Per Joystick (ThumbStick): Diesen Knopf kann man in zwei Richtungen (vorne/hinten, links/rechts) bewegen, außerdem unterschiedlich stark drücken. Also kann sich der Spieler in diesem Fall unterschiedlich schnell bewegen.

Um dies nun auf einen gemeinsamen Nenner zu bringen, kannst du ein Interface deklarieren, das zwei Zahlen angibt, und zwar je im Bereich von -1 bis 1 für die beiden Achsen. Der Joystick kann dieses Interface direkt beliefern, da es ja seinem Aufbau entspricht. Die Keyboard-Implementierung kann keine Zwischenschritte anzeigen, also gibt sie für die Eigenschaften einfach immer nur -1, 0 oder 1 zurück.

So habe ich ein Jump'n'Run-Spiel für Android und Desktop programmiert. Der gemeinsame Code stellt Schnittstellen bereit, die genau das beinhalten, was das Spiel wirklich braucht, und die Implementierungen für die beiden Plattformen stellen diese Informationen bzw. Methoden bereit.


Hi,

also bei meinem Spiel siehts im Moment so aus, dass mit den Thumbsticks in jede beliebige Richtung gefahren werden kann. Bei den Pfeiltasten dann nur in 8 Richtungen. So hatte ich mir das ja auch gedacht, dass über die Methoden der Tastatur Klasse, feste Werte zurückgegeben werden. Aber danke für den Tipp, vielleicht kann ich doch auf die ein odere andere Methode verzichten, bzw. die Klassen sinnvoll umschreiben, dass ich die Generics dann nicht mehr brauche. Jedoch würde mich trotzdem Interessieren, wie ich die Klassen KeyboardState und GamepadState von einer gemeinsamen Basis ableite, so wie das TH69 mit der StateBase Klasse gemeint hat. Vielleicht stell ich ja im Verlauf der Programmierung fest, doch so eine State Klasse zu benötigen :)
Yogu
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2598
Erhaltene Danke: 156

Ubuntu 13.04, Win 7
C# (VS 2013)
BeitragVerfasst: Di 06.12.11 22:52 
user profile iconScari hat folgendes geschrieben Zum zitierten Posting springen:
Vielleicht stell ich ja im Verlauf der Programmierung fest, doch so eine State Klasse zu benötigen :)

Dann kannst du ja einfach das Interface IInput erweitern :idea:
Scari Threadstarter
Hält's aus hier
Beiträge: 6



BeitragVerfasst: Di 06.12.11 23:06 
user profile iconYogu hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconScari hat folgendes geschrieben Zum zitierten Posting springen:
Vielleicht stell ich ja im Verlauf der Programmierung fest, doch so eine State Klasse zu benötigen :)

Dann kannst du ja einfach das Interface IInput erweitern :idea:


Ja aber ich weiß ja noch gar nich wie ich eine gemeinsame Klasse für die beiden States mache, so wie das TH69 meinte.
Die leiten ja nich von einer gemeinsamen Basisklasse ab.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4805
Erhaltene Danke: 1061

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mi 07.12.11 13:18 
Hallo Scari,

wie sehen denn deine Klassen KeyboardState und GamepadState bisher aus?
Wenn du diese beiden Klassen ja für das generische Interface IInput<T> verwenden willst, dann müssen die beiden davon abgeleiteten Klassen Input_PC und Input_XBox ja nach außen hin eine gemeinsame Schnittstelle anbieten. Und daher macht bisher die Methode "T getState()" keinen Sinn, denn dann müßte die Player-Klasse ja wiederum jeweils wissen, mit welcher der beiden State-Klassen gerade gearbeitet wird.
Gäbe es dagegen eine nicht-generische Basisklasse für die beiden State-Klassen, so könnte die Player-Klasse dann auf dieser Basis damit arbeiten.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mi 07.12.11 13:26 
Zitat:
wie sehen denn deine Klassen KeyboardState und GamepadState bisher aus?


Deine ... äh Seine? Das trifft es nicht ;)

msdn.microsoft.com/e...t.keyboardstate.aspx

msdn.microsoft.com/e...ut.gamepadstate.aspx
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4805
Erhaltene Danke: 1061

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mi 07.12.11 13:39 
Ups, jetzt verstehe ich, dies sind also XNA-Strukturen.
Ja, dann wird es nichts mit Basisklasse erstellen ;-)

Dann wäre es m.E. das günstigste, man würde ein Mapping erstellen, welches die GamePadStates auf die zugehörigen KeyboardStates abbildet (bzw. umgekehrt) und dann vom Programm aus nur eine von beiden benutzt. Bzw. so wie Yogu vorgeschlagen hat, ein eigenes Interface anzubieten.
Scari Threadstarter
Hält's aus hier
Beiträge: 6



BeitragVerfasst: So 11.12.11 20:49 
user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Ups, jetzt verstehe ich, dies sind also XNA-Strukturen.
Ja, dann wird es nichts mit Basisklasse erstellen ;-)

Dann wäre es m.E. das günstigste, man würde ein Mapping erstellen, welches die GamePadStates auf die zugehörigen KeyboardStates abbildet (bzw. umgekehrt) und dann vom Programm aus nur eine von beiden benutzt. Bzw. so wie Yogu vorgeschlagen hat, ein eigenes Interface anzubieten.


Achso ich dachte du wüsstest das :) Aber danke für die Hilfe, jetzt geht alles so wie ich es wollte.