Frage Cast / Generische Klasse
-
Hallo,
ich möchte Instanzen einer generischen Klasse in einer Liste halten und zur Laufzeit dessen Werte setzen. Folgendes Minimalbeispiel:
namespace Mini { public class Generic<TYPE> { public string Name { get; } private TYPE? Value; public Generic(string name) { Name = name; } public TYPE? GetValue() { return Value; } public void SetValue(TYPE? value) { Value = value; } } }
namespace Mini { public class MyString : Generic<string> { public MyString() : base(nameof(MyString)) { } } }
namespace Mini { public class Program { public static void Main(string[] args) { var genericList = new List<Generic<dynamic>>(); var myString = new MyString(); genericList.Add(myString); } } }
In Zeile 10 des letzten Quelltextes meckert der Compiler. Wie muss ich die Liste deklarieren, so dass ich die Instanz <myString> anhängen kann? Kann jemand helfen?
Vielen Dank im Voraus
VG Torsten
-
Dieser Beitrag wurde gelöscht!
-
Einfach
var genericList = new List<MyString>();
Also den konkreten Datentyp angeben, wie sonst auch.
Alternativ geht auchvar genericList = new List<Generic<string>>();
(also die Basisklasse)
Oder willst du komplett verschiedene Instanzen von
Generic<TYPE>
in einer Liste halten?
Dann erzeuge eine nicht-generische Basisklasse (bzw. Interface) und benutze diese dannpublic class GenericBase // oder interface IGeneric { // ... } public class Generic<TYPE> : GenericBase { } // in Main var genericList = new List<GenericBase>(); var myString = new MyString(); var myInt = new Generic<int>(""); genericList.Add(myString); genericList.Add(myInt);
-
@Th69 Ja, ich muss verschiedene Datentypen vorhalten. An ein Interface hatte ich auch gedacht, dann muss ich aber beim Holen der Werte mit GetValue() jedes Mal einen Cast machen. Ich hatte gehofft, das umgehen zu können.
VG Torsten
-
@TorDev
Vielleicht gibt es eine andere elegante Möglichkeit. Dazu müsste man aber wissen wie du die Liste verwenden willst.
-
Mein Ziel ist es, alle möglichen Parameter einer Software zu "registrieren", damit ich Hilfe, Dokumentation, etc. automatisiert generieren kann. Ich versuche jetzt den Ansatz, dass ich den zu setzenden Wert als <object?> deklariere. Mal schauen, ob das halbwegs annehmbar ist.
VG Torsten
-
Ohne casten/reflection wirst du das wohl nie hinbekommen.
Ausgenommen wenn für "damit ich Hilfe, Dokumentation, etc. automatisiert generieren kann"
nur eine bestimmte Anzahl von funktionen/Daten benötigt werden.
Dann könnte man dass über ein Interface lösen, welches von jeder Parameterklasse implementiert wird.Mal eine ganz plumpe definition eines Interfaces anhand deiner "Generic" Klasse:
und den Beispielen was generiert werden sollinterface ParamaterInformation { string Name { get; } object? GetValue() void SetValue(object? value); string GetHelp(); string GetDocumentation(); }
-
@TorDev sagte in Frage Cast / Generische Klasse:
Mein Ziel ist es, alle möglichen Parameter einer Software zu "registrieren", damit ich Hilfe, Dokumentation, etc. automatisiert generieren kann.
OK. Da spielt der Typ aber erstmal noch keine Rolle. Bei
parameters["foo"].DisplayName
,parameters["foo"].Category
,parameters["foo"].Description
etc. ist der Typ des Parameters ja erstmal noch egal.Ich versuche jetzt den Ansatz, dass ich den zu setzenden Wert als <object?> deklariere. Mal schauen, ob das halbwegs annehmbar ist.
Kannst du mal skizzieren wie so ein Zugriff aussehen soll?
Weil ich denke mir du hast im Prinzip nur zwei Fälle: welche wo du z.B. Name des Parameters und Wert als String reinbekommst. Und welche wo du den Typ kennst und den Wert direkt setzen willst.
Im ersten Fall kannst du schön mit einem vom Typ unabhängigen Interface arbeiten. Damit kannst du dann z.B.
parameters[nameString].SetValue(valueString);
schreiben.Und im zweiten Fall kannst du der Klasse die die Parameter verwaltet eine Hilfsfunktion pro Typ spendieren. Damit du dann sowas wie
parameters.GetIntParam("foo").SetValue(42);
schreiben kannst. Oder evtl. auchparameters.Get<int>("foo").SetValue(42);
.
-
G' Morgen,
es ist der Ansatz, wie @firefly ihn beschrieben hat. So richtig elegant ist es leider nicht, da ich relativ viel Overhead durch Casten erzeuge.
Ich hatte auch vergessen, zu erwähnen, dass ich natürlich auch weitere Informationen für die Software selbst bereitstellen möchte. Nämlich gesetzter Wert, mögliche Standardwerte, usw.
VG Torsten
-
Wie meinst du ohne Casten auskommen zu können?
Ja ein Cast hat einen gewissen overhead. Aber da es sich hier um Parameter handelt, sollten die stellen, an denen ein Cast notwendig ist, sehr wenige sein.
Spätestens wenn der Wert des Parameters genutzt werden soll muss der Wert in dem benötigten Datentyp vorliegen.Wenn der Overhead eines Cast zu einem Problem wird (z.b. Jedes mal wenn eine Berechnung auf den Parameterwert zugreift), dann ist die grundlegende Verwendung der Parameter das Problem.
@TorDev sagte in Frage Cast / Generische Klasse:
Nämlich gesetzter Wert, mögliche Standardwerte, usw.
An den stellen, wo ein Cast ein zu größer Overhead wäre, sollte nur ein einmalig Cast benötigt werden, und nicht ständig, wenn auf die Werte zugegriffen werden soll.
An solchen Stellen sollte das Ergebnis des Cast gecached werden.An allen anderen Stellen (z.b. eine Liste aller Parameter mit der Option die Werte zu ändern) ist ein Cast kein Performance problem.
-
@TorDev
Ich verstehe immer noch nicht wo du Casts brauchst.
Hast du meinen Beitrag gelesen und verstanden?So richtig elegant ist es leider nicht, da ich relativ viel Overhead durch Casten erzeuge.
Ich hab's noch nicht selbst ausprobiert, aber anhand von Artikeln die ich über
dynamic
gelesen habe würde ich annehmen dass der Overhead vondynamic
noch viel schlimmer ist.
-
@hustbaer Mein Ziel ist es auch, die Instanzen der Parameter als Wertehalter zu nutzen.
Beispiel:
Zum Programmstart wird eine Instanz des generischen Parameters <ResourcesPath> erzeugt. Diese Instanz ist statisch. Nun kann ich beispielsweise die Kommandozeilenparameter einlesen und prüfen, ob <--resourcesPath=C:> vom Anwender mitgegeben wurde. Ist das der Fall, setze ich den Parameter <ResourcesPath> auf den Wert <C:>. Wurde der Parameter vom Aufrufer nicht mitgeben, wird ein Standardwert (z.B.: C.\Resources) gesetzt, bzw. bei ResourcesPath.GetValue() zurückgelierfert.
Wenn ich es weder generisch, noch über einen objektorientierten Ansatz hinbekomme, kann ich nur Strings als Werte in den jeweiligen Parametern speichern. Mein Ziel bzw. mein Wunsch ist es, ganz beliebige Datentypen zu setzen (Integerwerte, Instanzen komplexer Klassen, etc.).
In einfachen Fällen könnte ich sowas wie GetIntValue(), GetBoolValue(), etc. nutzen. Will ich komplexere Datentypen nutzen, müsste ich jedesmal neue Klassen a la <ComplexParameter> schreiben, die von der Basisklasse <Parameter> ableiten.
Ist das halbwegs verständlich, was ich meine?
VG Torsten
-
Ich denke schon dass ich verstehe was du machen willst.
Nur nicht wieso du meinst dadynamic
oder Casts zu brauchen.Das Setzen der Werte wird wohl meist über Strings passieren - Kommandozeile, Configfile - das ist ja alles textbasiert. Und beim Lesen wirst du die Objekte ja nicht per Name nachschlagen müssen. D.h. dort kannst du direkt auf die abgeleitete Klasse zugreifen.
public interface IParameterBase { string Name { get; } string Description { get; } string DefaultValueString { get; } string ValueString { get; set; } void ResetToDefault(); } public interface IParameter<T> : IParameterBase { T Value { get; set; } } public class IntParameter : IParameter<int> { public string Name { get; private set; } public string Description { get; private set; } public string DefaultValueString { get { return m_defaultValue.ToString(); } } public string ValueString { get { return m_value.ToString(); } set { m_value = int.Parse(value); } } public int Value { get { return m_value; } set { m_value = value; } } public IntParameter(string name, string description, int defaultValue) { Name = name; Description = description; m_defaultValue = defaultValue; m_value = defaultValue; } public void ResetToDefault() { m_value = m_defaultValue; } private int m_defaultValue; private int m_value; } public class StringParameter : IParameter<string> { public string Name { get; private set; } public string Description { get; private set; } public string DefaultValueString { get { return m_defaultValue; } } public string ValueString { get { return m_value; } set { m_value = value; } } public string Value { get { return m_value; } set { m_value = value; } } public StringParameter(string name, string description, string defaultValue) { Name = name; Description = description; m_defaultValue = defaultValue; m_value = defaultValue; } public void ResetToDefault() { m_value = m_defaultValue; } private string m_defaultValue; private string m_value; } public class MyParameters { public IntParameter FooCount { get; private set; } public StringParameter BarPath { get; private set; } public MyParameters() { FooCount = Add(new IntParameter("foo_count", "Initial number of foo instances", 3)); BarPath = Add(new StringParameter("bar", "Path of the bar directory", "C:\\Bar")); } public void SetValue(string name, string value) { m_parameters[name].ValueString = value; } // if necessary: public IParameter<T> Get<T>(string name) { // yes, this is a cast, but only in one central place. // and I don't think this function would be needed often - if at all. return (IParameter<T>)m_parameters[name]; } private T Add<T>(T p) where T : IParameterBase { m_parameters.Add(p.Name, p); return p; } private readonly Dictionary<string, IParameterBase> m_parameters = new Dictionary<string, IParameterBase>(); } class Usage { public static void Example(MyParameters param) { param.SetValue("foo_count", "123"); // use string API to set value from cmdline/config-file int i = param.FooCount.Value; // get value by accessing object directly } }