Jump to content
Unity Insider Forum

Parameter generischer Klassen dynamisch erstellen


Kurumi-chan

Recommended Posts

Hey Leute,

ich würde gerne wissen, ob es möglich ist einen Parameter einer generischen Klassen dynamisch zu erstellen? Als Beispiel:

internal class DatabaseEditorSubWindow<DB> : IDatabase where DB : ScriptableObject {} //Wird aufgerufen, wenn ein bestimmter Button geklickt wird. Der Typ der Datenbank wird mitgegeben 

Hier wird nun geschaut, welche Daten die Datenbank enthält. Dies können z.B. Listen verschiedenen Typs sein. Diese werden geladen und deren Inhalte in eine Variable gepackt. Anschließend werden Buttons generiert, mit denen die Einzelnen Inhalte geladen werden sollen. Dies geschieht dann über eine neue Klasse.

internal class DatabaseCoreWindow<DB, TType> : IDatabase where DB : ScriptableObject where TType ScriptableObject, IName {} // Zeigt die Daten an, erstellt neue Inhalte, usw..

Der Aufruf der Klasse sieht folgendermaßen aus

m_Child = new DatabaseCoreWindow<DB, (Type)toolbarTypes[i]>(m_Database, toolbarList); // Hier soll TType dynamisch gesetzt und durch den Typ der Liste ersetzt werden, z.B. Item, oder Skill

Ich habe dazu verschiedenste Ansätze versucht, aber nichts hat funktioniert. Entweder es kommen Fehler, oder es werden die mitgegebenen Parameter nicht gesetzt.

Hat evtl. jemand eine Idee, wie ich das umsetzten kann?

Vielen Dank für die Hilfe.

LG 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Moin!

Ich bin gerade nicht so ganz fit, daher verstehe ich die Frage vielleicht nur so halb. Aber wenn ich die Hälfte immerhin richtig verstehe, dann geht es darum, ein System.Type zu haben und diesen als generischen Parameter zu nehmen. Das ist nicht vorgesehen, da Generics ein "statisches" Konzept ist, das zur Compile Time feststeht und dadurch mehr Sicherheit gibt als dynamisches Umherwerfen von Typen zur Laufzeit.

Der korrekte Weg ist daher eine Überladung der Methode mit einem normalen Parameter vom Typ System.Type. Macht GetComponent auch so (wobei "Unity macht das auch so" natürlich kein starkes Argument ist :D).

Alternativ geht das theoretisch auch mit Reflection. Man kann da aber den generischen Typ (typeof(DatabaseCodeWindow)) nehmen und den mit MakeGenericType() inflaten (also generische Parameter setzen). Diesen Typ kannst du dann in den Activator stecken, um eine Instanz zu erzeugen. Reflection ist aber nur in einigen wenigen Sonderfällen eine gute Idee, z.B. wenn dein Code mit anderem Code funktionieren soll, den du jetzt noch nicht kennst, z.B. weil du ein Paket schreibst, das andere dann weiter verwenden. Wenn das nicht der Fall ist, solltest du Reflection eher meiden.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hey Sascha, 

danke dir für deine Antwort. Ja, habe schon befürchtet, bzw. gelesen und gesehen, dass es so nicht funktioniert, wie ich es mir wünsche. Aber hatte die Hoffnung, dass wenn ich direkt jemanden danach Frage, derjenige evtl. doch einen Tipp, oder Idee hat. Schade, dann werde ich wohl doch mit switches arbeiten müssen. Wäre anders ja auch zu einfach gewesen^^

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 1 Stunde schrieb Kurumi-chan:

Schade, dann werde ich wohl doch mit switches arbeiten müssen.

Hmm... müssen tut man das nie. Ich bin mir gerade nicht ganz sicher, aber mir fällt gerade kein Fall ein, wo man etwas mit einer generischen Methode machen kann, was man nicht auch mit einem System-Type-Parameter machen könnte. Außer Boxing bei Structs vermeiden... :D

Was machst du denn am Ende mit deinem generischen Typparameter, dass du nicht auch ein System.Type-Objekt dafür nehmen kannst?

Und so nebenbei: switch-case kannst du üblicherweise mit einem Dictionary vermeiden. Sollte man machen, wenn das switch-case potentiell immer weiter wachsen würde.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Der Parameter gibt den Typ des Datanbankobjekts an, also z.B. Item, Skill, Slot, usw.. Und davon wird dann jeweils die Liste an verfügbaren Objekten geladen und sobald ich eines aus der Liste anklicke, wird der entsprechende Inspector aufgerufen. 

Nur muss dafür ja der genaue Type bekannt sein, sonst bekomme ich die Daten ja nicht?

Die Datenbank ist so aufgebaut, das im Db Objekt die verschiedenen Datenbanken liegen, also z.B. ItemDatabase, SettingDatabase, usw.. Und darin sind dann mehrere Listen. Im Fall ItemDatabase wären das Item, Slot, Currency. Die einzelnen Listen generieren zum einen einen neuen Menüpunkt und sobald man einen davon klickt, wird die entsprechende Liste geladen und angezeigt, sowie der erste Eintrag der Liste geladen. Wenn man nun in der Liste einen anderen eintrag wählt, wird halt dieser geladen, usw.

Ps: Das Bild nicht auf die Goldwaage legen, das ist alles noch nicht fertig. 😄

Unbenannt-1.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 21 Minuten schrieb Kurumi-chan:

Nur muss dafür ja der genaue Type bekannt sein, sonst bekomme ich die Daten ja nicht?

Korrekt! Aber du kannst Typen eben nicht nur über generische Parameter kommunizieren, sondern auch über einen normalen System.Type-Parameter.

Statt

public void Foo<T>()

machst du

public void Foo(System.Type type)

Du kannst generische Parameter auch problemlos mit typeof() zu einem System.Type-Objekt umwandeln. So als Beispiel:

public bool Foo<T>(System.Type type)
{
  return typeof(t) == type;
}

Das gibt dann true zurück, wenn man das so aufruft:

Foo<Light>(typeof(Light))
// oder
Foo<Light>(GetComponent<Light>().GetType())

Umgekehrt braucht man halt leider Reflection und sollte das vermeiden. Aber man kann oft von der anderen Seite ankommen, z.B. so:

class MyGenericClass<T>
{
  public System.Type GenericType => typeof(T);
}

Objekte dieser Klasse geben die mit der Property "GenericType" den Typ zurück, den du beim Erstellen für T übergeben hast:

var thing = new MyGenericClass<Light>();
Debug.Log(thing.GenericType.Name); // "Light"

Damit geht eine ganze Menge. Du könntest zum Beispiel beim Start alle deine Objekte laden und sie in ein Dictionary packen.

Das Dictionary kann so aussehen:

private Dictionary<System.Type, List<MyGenericClass<>> genericClassObjectsByGenericType = new Dictionary<...>();

Und dann lädst du da alle geladenen Objekte rein und sortierst sie nach ihrem generischen Typ:

foreach (var loadedObject in allLoadedObjects)
{
  var type = loadedObject.GenericType;
  List<MyGenericClass<>> list;
  if (genericClassObjectByType.TryGetValue(type, out list))
  {
    list.Add(loadedObject);
  }
  else
  {
    list = new List<MyGenericClass<>>();
    list.Add(loadedObject);
    genericClassObjectByType.Add(type, list);
  }
}

Und wenn du dann alle Objekte eines bestimmten generischen Type haben willst, holst du sie dir einfach aus dem Dictionary:

public List<MyGenericClass<>> GetAllObjectsForType(System.Type type)
{
  return genericClassObjectsByType[type];
}

Da fehlt noch die Ausnahmebehandlung, falls der Typ nicht gefunden wird. Und ich hab das jetzt so runtergetippt, kann sein dass die Syntax mit <> nicht ganz richtig verwendet wird. Aber ich hoffe, die Idee ist einigermaßen klar.

vor 1 Minute schrieb Kurumi-chan:

Wäre das nicht selbe nur in Grün? Ich müsste ja auch dort den Datentypen angeben.

Ja schon, aber eine Datenstruktur wachsen zu lassen ist besserer Stil als ein switch-case, das ständig erweitert werden muss. Vor allem, weil du bei einem Dictionary einfach mit der Schleife über alle Typen gehen kannst (siehe oben), anstatt deinen switch-case-Code jedes Mal anzufassen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...