Jump to content
Unity Insider Forum

Methoden mit unbestimmten Rückgabewert


Singular

Recommended Posts

Hallo zusammen, auch wenn die Frage von meinem Problem her verwand ist mit meiner letzten Frage schmeiße ich das mal kurz in ein neues Thema:

Ist es Möglich, dass eine Methode einen Rückgabewert hat, der aber nicht vorgegebene Klasse (wie in meinem Fall)i ist?

Heißt: Ich habe eine Klasse geschrieben, die mehrere Klassen speichern kann. Dafür hat dieser Speicher verschiedene Variablen, wovon einer (in Zahlen 1) belegt wird.

Jetzt möchte ich eine Methode schreiben, die nun diese gespeicherte Klasse zurückgibt. Da es aber 6 verschiedene sein können soll nur eine, und zwar die beschriebene, Klasse zurück gegeben werden. Die Methode davor muss dann selber zusehen, das die damit macht, ob sie was damit anfachen kann.

 

Ich hoffe es ist klar geworden. Hier mal kurz das Script für meinen "ItemSpeicher":

public class ItemSpeicher
{
    public ItemType itemtype;

    public Core core;
    public Weapon weapon;
    public Spezial spezial;
    public FTL ftl;
    public Engine engine;
    public Hull hull;

    public ItemSpeicher(Core core)
    {
        core = this.core;
        itemtype = ItemType.Core;
    }

    public ItemSpeicher(Weapon weapon)
    {
        weapon = this.weapon;
        itemtype = ItemType.Weapon;
    }

    public ItemSpeicher(Spezial spezial)
    {
        spezial = this.spezial;
        itemtype = ItemType.Spezial;
    }

    public ItemSpeicher(FTL ftl)
    {
        ftl = this.ftl;
        itemtype = ItemType.FTL;
    }

    public ItemSpeicher(Engine engine)
    {
        engine = this.engine;
        itemtype = ItemType.Engine;
    }

    public ItemSpeicher(Hull hull)
    {
        hull = this.hull;
        itemtype = ItemType.Hull;
    }
  
  	//Methode mit unbestimmten Rückgabewert:
  	public "unbestimmte Rückgabe" GetItem()
    {
      switch(ItemType.itemtype)
      {
          case ItemType.Weapon:
          		return weapon;
          		break;
          
          //[...]
      }
   }   
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 2 Stunden schrieb Singular:

Ist es Möglich, dass eine Methode einen Rückgabewert hat, der aber nicht vorgegebene Klasse (wie in meinem Fall)i ist?

Ja und nein. C# ist eine typstarke Sprache. Das heißt, dass alle Variablen, Felder, Parameter und Funktionen (also Methoden, die etwas zurückgeben) einen Typ deklariert haben müssen.

...aber...

C# ist auch eine objektorientierte Sprache. Deine Komponente erbt von MonoBehaviour, erbt von UnityEngine.Object, erbt (iirc) von System.Object. Und am Ende dieser Kette kommt immer System.Object. Sogar primitive Typen wie int und float erben davon. Was man machen kann ist, dass man einen gemeinsamen Supertyp benutzt:

class Weapon : Item
class Potion : Item
class Armor : Item

Dann gibt deine Methode ein Objekt vom Typ "Item" zurück. Und weil Vererbung "ist-ein" bedeutet (ein "Weapon" ist-ein "Item"), kann das Item eben eine Waffe oder ein Rüstungsteil sein. Wenn aber jetzt der Code, der den Rückgabewert in die Hand kriegt, eine Referenz auf eine Waffe erwartet, gibt's nen Compilerfehler - denn es könnte ja ein Trank sein, der da zurück kommt.

C# ist aber auch eine Sprache, die Generics versteht. Man kann einen Typ (oder mehrere) in einer Methodensignatur definieren und diesen dann im Rest der Signatur und im Rumpf benutzen:

public T FindAThing<T>()

Der generische Parameter "T" steht für einen Typ, der hier nicht weiter definiert ist, aber die Methode soll bitte ein Objekt dieses Typs zurückgeben. Der Typ wird dann woanders definiert, z.B. beim Aufruf der Methode. Kennst du vielleicht von GetComponent.

Light light = GetComponent<Light>(); // Geht
Collider collider = GetComponent<Light>(); // Aua

Die beiden Zeilen rufen dieselbe Methode auf, aber die untere Zeile wird nicht einmal kompiliert, weil der erwartete Typ (Collider) und der Rückgabetyp (Light) nicht passen. Die Methode gibt also unterschiedliche Dinge zurück, je nach generischem Parameter.

Du kannst das auch weiter spezifizieren:

public T GetItem<T>() where T : Item

Hier beschränkst du T auf alle Typen, die von "Item" erben (und Item selbst). GetComponent hat z.B. das hier:

public T GetComponent<T>() where T : Component

Sodass GetComponent meckert, wenn du GetComponent<GameObject>() schreibst, weil GameObject keine Component ist.

Vielleicht auch interessant ist Instantiate. Instantiate gibt ja immer etwas von dem Typen zurück, den man reinsteckt:

public MyComponent prefab;

 

MyComponent instance = Instantiate(prefab); // oh yes
GameObject goInstance = Instantiate(prefab); // oh no

Das machen die so:

public T Instantiate<T>(T original) where T : UnityEngine.Object

Man kann einen generischen Typen also auch als Parameter benutzen, und dieser ist dann der gleiche Typ wie beim Rückgabewert. Hier also: Was reinkommt, kommt auch raus... zumindest Typ-weise. Wenn C# dann den Typen aus dem Parameterwert herleiten kann, braucht man den Parameter beim Aufruf auch nicht mehr explizit in die spitzen Klammern schreiben. Aber... das führt schon etwas weit.

In deinem Fall ginge also

public T GetItem<T>() where T : Item

Aber was macht man damit jetzt im Rumpf der Methode? Zuerst einmal kannst du T als Variablentyp benutzen:

T thing = GetATFromSomewhere();

Dann kannst du den generischen Parameter in ein System.Type-Objekt umwandeln:

System.Type type = typeof(T);

Und jetzt wird's leider knifflig und sogar ein bisschen hässlich. Man kann nämlich nicht einfach schreiben:

return (T)myWeapon;

Das wäre unsicher. Man kann das aber enforcen:

return (T)System.Convert.ChangeType(myWeapon, typeof(T));

Da muss man dann nur echt aufpassen, dass T auch wirklich "Weapon" ist.

if (typeof(T) == typeof(Weapon))
{
  return (T)System.Convert.ChangeType(weapon, typeof(T));
}

Switch-Statement wird da schwieriger, aber man kann da evtl. auf Strings ausweichen. Hier ist sowieso alles unsicher, da machen Strings den Kohl auch nicht mehr... mager. Und man kann mit nameof ein bisschen den Schaden mitigieren.

switch (typeof(T).Name)
{
  case nameof(Weapon):
    return (T)System.Convert.ChangeType(weapon, typeof(T));
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Sascha, ich finde es immer erstaunlich, wie schnell du solche Texte runter tippst. Entweder hast du das hier schon 10 mal gekostet und STRG + C sei dank schnell reinkopieren können oder du denkst schon gar nicht mehr darüber nach sondern Lebst das ganze. Wieder einmal vielen Dank für deine Hilfe.

Ich habe heute festgestellt, dass ich heute wahrscheinlich gar nicht mehr an den Rechner komme um weiter programmieren zu können, deswegen nur schnell vom Handy:

Ich habe leider nicht hundertprozent von dem Verstanden was da alles drin war und wahrscheinlich sobald ich es selber anwenden kann. Vielen Dank und ich melde mich bestimmt nochmal zu dem Thema ;)

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 2 Stunden schrieb Singular:

oder du denkst schon gar nicht mehr darüber nach sondern Lebst das ganze

Eher diese Variante. Für die Sachen, die ich zu schon oft geschrieben habe, habe ich ja meinen Blog gemacht :)

vor 2 Stunden schrieb Singular:

Ich habe leider nicht hundertprozent von dem Verstanden was da alles drin war und wahrscheinlich sobald ich es selber anwenden kann. Vielen Dank und ich melde mich bestimmt nochmal zu dem Thema

Gerne, und mach das. Generics sind auch so ein Thema, das nicht wirklich mehr für Anfänger ist ;)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ah okay, jetzt bin ich schon mal schlauer. Hat mir keiner Ruhe gelassen also hab ich mir jetzt doch noch schnell eine Stunde verschafft. ;)

Hat soweit (hätte ich auch nicht anders erwartet) alles geklappt. ABER, und das ist ein Problem. Um nun die Methode aufrufen zu können, muss ich schreiben

itemSpeicher.GetItem<Weapon>();

Das funktioniert zwar, und er würde mir dann sicherlich auch meine gespeicherte Klasse Weapon zurück geben. Das möchte ich aber nicht. Ich möchte lediglich, dass Script A vom ItemSpeicher die GetItem() Methode aufruft und die Klasse, die gespeichert ist, zurück gibt. Ist das eine Weapon, dann ist das eine Weapon. Ist das ein Core, dann ist das ein Core.

In dem Moment, wo ich die Methode aufrufe, weiß ich noch gar nicht was ich überhaupt bekomme. gerade in ein meiner foreach Schleife, in der in mehrere ItemSpeicher durchschleife um die ins Inventar zu legen würde diese Methode leider keinen Sinn machen.

Gibt es also nun einen Weg, ein unerwartetes Ergebnis zu erhalten?

Edit: Sorry jetzt habe ich nicht gesehen, dass du schon zwischenzeitig schon geantwortet hast. :D

Link zu diesem Kommentar
Auf anderen Seiten teilen

Richtig. Alle meine Items erben auch von "Item". Problem ist aber, welchen Rückgabewert gebe ich denn der Methode?

public Item GetItem()

würde mir nur das Grundgerüst zurück geben aber nicht alles was danach kommt. Wenn das ganze also eine Weapon ist, kommte ich nicht mehr an die damage variable dran, weil es ja nur ein Item von der Klasse Item ist. Und das hat nunmal keine variable hat, die damage heißt. 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du willst keinesfalls mit instanceof anfangen. Wenn du eine Methode hast, die ein Ding zurückgibt, und du weißt nicht, welchen Typ das Ding hat, dann willst du nur mit dem Teil arbeiten, den du kennst. Also wenn du (irgend)ein Item kriegst, dann willst du auch nur mit Item arbeiten. Dann zu schauen, ob es sich um eine Waffe handelt oder nicht, und dann davon abhängig anderen Code laufen lassen, ist ganz ganz schlecht. Drumherum zu bauen ist oft etwas umständlich, muss aber sein. Was willst du denn mit dem Item machen?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Natürlich kann ich vorhersagen und überprüfen, was ein ItemSpeicher zurück geben wird, aber darum geht es ja, dass ich das nicht ständig machen muss. Wenn mein Inventar generiert wird, möchte ich einfach nur den ItemSpeicher aufrufen, bzw die Methode und das jeweilige Item generieren.

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 36 Minuten schrieb chrische5:

Auch wenn mein Tipp Mist gewesen ist, verstehe ich immer noch nicht so richtig, was du genau machen willst.

Alles Gut :D

Also: Ich habe ein inventar mit mehreren Slots. Darauf werden Images erstellt, die das jeweilie Item (Weapon, Hull, Core...) gespeichert haben. Um nun zu verhindern, dass jedes Image nun alle Klassen speichern können muss, möchte ich, dass nur eins (ItemSpeicher) gespeichert wird. Im Zuge der Generierung der ganzen Items wird über eine Foreach Schleife nachgefragt was das für ein Item ist, und entspechend der Tooltip und alles drum herum aufgebaut.

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 5 Minuten schrieb Sascha:

Klingt danach, als würdest du einfach ein Sprite in deine Item-Klasse packen wollen. Dein Slot liest dann den Sprite aus und zeigt ihn an.

Ne da liegt das ganze Item drin. Das Image kann über Drag and Drop in einen Slot bewegt werden, der das dan an den Spieler weiter gibt um ihn mit den neuen Werten von dem Gegenstand auszustatten. Ja ein Sprite ist da auch drin aber um das geht es mit nicht einmal.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Vielleicht nochmal zur Ergänzung. Das ganze funktioniert bereits allerdings sehr kompliziert. Derzeit ist es leider so, dass jedes Script, dass mit Items interagiert 6 propertys evtl 12 haben muss. Egal ob es jetzt Listen sind oder einfach Variablen, die eine Klasse : Item speichert. Wenn ich also mein Item innerhalb meines Inventars bewege (also eigentlich das Sprite) hat dies alle Daten darüber was es ist UND darüber was es sein könnte. Und das trifft auf jedes Object zu was mit Items zu tun hat. Außerdem muss dabei immer geprüft werden, was ist das denn jetzt genau und dann auch abgespeichert werden.

Um das ganze von 6 auf 1 zu reduzieren habe ich gedacht es wäre möglich, dass ganze über eine Klasse zu regeln, die selber dafür sorgt, dass diese Items abgespeichert werden und wenn jemand danach fragt das eine einzige abgespeichert Item zurück zu geben.

Darum die Frage ob es die Möglichkeit gibt einen Rückgabewert etwas offener zu gestalten.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das geht auf jeden Fall besser, aber deine Herangehensweise wird vermutlich nicht funktionieren. Du solltest dich auf die Vererbungsstrategie konzentrieren und dir aneignen, wie man damit sinnvoll Dinge macht. "Mache etwas anderes, je nachdem welchen Typ ein Objekt hat", ist immer schlecht.

Link zu diesem Kommentar
Auf anderen Seiten teilen

🤔 Mmhhh...

Okay. Da ich mir sehr viel gerade selber aneigne und mir dieses System so selber ausgedacht habe und es funktioniert darf ich glaube ich trotzdem ein wenig Stolz sein ;)

Dass es nicht die beste Herangehensweise ist, ist mir tatsächlich bewusst. In Drei Jahren werde ich wahrscheinlich auch sagen: "Was war das denn für ein Quatsch den ich da zusammengeschrieben habe? Darauf war ich stolz? Autsch."

Das mein Spiel kein Meisterwerk ist und das derzeit nicht wird ist mir bewusst. ich mache weiter und werde weiter lernen und bestimmt irgendwann wissen wie es besser geht. Danke euch beiden auf jeden Fall schon mal für eure Hilfe. 

Am 15.3.2021 um 09:31 schrieb Sascha:

In deinem Fall ginge also



public T GetItem<T>() where T : Item

Das werde ich auf jeden Fall nutzen und schon mal schauen, wo ich das sonst noch nutzen kann, um etwas zu verbessern.

 

Achso, ich habe schon die nächste Frage aber die packe ich ein neues Thema ;)

Link zu diesem Kommentar
Auf anderen Seiten teilen

EDIT: Ich editiere diesen Post bald, damit keine Verwirrung entsteht, da ich nochmal gelesen habe.

Also ich muss sagen, was @Sascha gesagt hat ist schon richtig. Ich wollte früher sogar zu so einem Thema ein Video machen, aber kam nicht dazu, weil beim Erklären es sich etwas kompliziert anhört (da ich das per C# Console mache + Interface dazu zeige). Ein Beispiel aus dem eigentlichen Tutorial Code-Bespiel. Ich habe List<Item> inventory. Dort werden Sachen wie Weapon bzw. Sword, Bow und andere Dinge gepackt. Manche davon sind mit IRepairable, IDurability bestückt. Das heißt manche Items können nicht repariert werden. Beispiel Potions. In meinem Fall Bow lässt sich nicht reparieren, da es aus Holz ist. Das mit IRepairable können auch sogut wie Components sein (dazu unten mehr).

Nun ich gehe zu einem NPC und möchte ich reparieren lassen. Was ich tue ich nun?

IEnumerable<IRepairable> repairableItems = from item in inventory 
                                            where item is IRepairable select (IRepairable)item;
						// get auch where item is IRepairable select item as IRepairable;
  
foreach (var item in repairableItems) 
{
	item.Repair();
}

Schon lassen sich alle Waffen reparieren. Das lustige ist, in item (also beim Interface) steckt aber auch noch die jeweilige Instance mit drin. Heißt man könnte in der Schleife folgendes tun (sollte man aber nie, da sie wirklich unabhängig arbeiten sollten)

if(item is Sword sword)
{
  // mache etwas geiles
}

Aber ich möchte aber ein Item reparieren lassen. Sagen wir aus dem Inventory die Nummer 1 wurde gewählt:

IRepairable r = inventory[1] as IRepairable
if(r != null)
{
    r.Repair();
}

 

Nun, das Beispiel oben lässt sich auch mit Unity arbeiten, wenn man für ein Item eher Components benutzt. Also quasi

GameObject
-> Component Sword
-> Component Durability - Hält also nicht für immer
-> Component Repairable - Kann also repariert werden (wird wohl mit Durbility zusammen arbeiten :D)

zumindest mach ich das so. Dann geht folgendes.

Repairable bla = null;
IEnumerable<Repairable> repairableItems =  from item in inventory
                                            where (bla = item.GetComponent<Repairable>()) != null
                                            select bla;

oder einzeln:

Repairable r = inventory[1].GetComponent<Repairable>()
if(r != null)
{
    r.Repair();
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...