Autor Beitrag
Christian S.
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Sa 04.03.06 19:50 
Hallo!

Ich schreibe momentan an einer Klasse, welche dazu dient, dass die Benutzer meines Programm verschiedene Einstellungsprofile speichern können. Momentan werden Einstellungen ausschließlich als Strings geschrieben bzw. gelesen.
Das möchte ich nun ändern und hatte an eine Methode wie diese gedacht:

ausblenden C#-Quelltext
1:
public T ReadSetting<T>(string settingName, T def)					


Nun habe ich das Problem, dass ich sicherstellen muss, dass T die Methode "Parse" besitzt bzw. das ein Cast von String nach T existiert. Wie kann ich das am geschicktesten machen?

Grüße
Christian

P.S.: Ich könnte natürlich zur Laufzeit mittels Reflection die Methode "Parse" finden und aufrufen, aber zum einen ist es halt erst zur Laufzeit, zum anderen ist Reflection nicht gerade schnell. ;-)

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
jasocul
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 6386
Erhaltene Danke: 146

Windows 7 + Windows 10
Sydney Prof + CE
BeitragVerfasst: Sa 04.03.06 19:58 
Definiere dir doch eine Basisklasse, von der T abgeleitet ist. In dieser Basisklasse definierst die Methode Parse als abstract. Dann sind alle Nachkommen gezwungen die Methode Parse zu besitzen.
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Sa 04.03.06 20:02 
Hm. Damit könnte ich aber nur eigene Typen lesen. Was mache ich mit int, bool, double? Die schreibe ich eigentlich viel häufiger in die Datei als eigene Typen.

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
Robert_G
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 416


Delphi32 (D2005 PE); Chrome/C# (VS2003 E/A, VS2005)
BeitragVerfasst: So 05.03.06 10:12 
Das Prüfen auf den cast kannst du knicken. Den Rest kann man schnieke über type constraints lösen.
Pseudo code:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
interface ISetting<T>
  where T : ISetting<T> //rekursive constraint, den Sinn erkennst du beim Implementieren ;)
{
  IEnumerable<T> Parse(string text);
}

class Settings<T>
  where T : ISetting<T>, new()
{
}

Falls du das statische parse meinst, kannst du es ebenfalls knicken. Statische member(somit auch operatoren) kommen erst mit .Net3 für interfaces.
Da statische member immer nur exakt auf den typen wirken, der sie deklariert hat, wäre höchstens reflection ein Weg für dich...
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: So 05.03.06 12:44 
Wenn ich das richtig sehe, ist aber auch das nur für selbstgebaute Typen möglich, oder?

Motzi hatte mir den Tipp gegeben, die vorhandenen Typen zu kaspeln, was ich dann so machen wollte:
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
    public interface ISetting<T>
      where T : ISetting<T> //rekursive constraint, den Sinn erkennst du beim Implementieren ;)
    {
        T Parse(string text);
    }

    public struct MyInt : ISetting<MyInt>
    {
        public int value;

        public MyInt(int Value)
        {
            value = Value;
        }

        public static implicit operator MyInt(int aInt)
        {
            return new MyInt(aInt);
        }
        public static implicit operator int(MyInt aMyInt)
        {
            return aMyInt.value;
        }

        #region ISetting<MyInt> Members

        public MyInt Parse(string text)
        {
            return Int32.Parse(text);
        }

        #endregion
    }
(Das Interface hatte IMHO einen kleinen Fehler in Deinem Posting)

Obiges funktioniert zwar, aber leider ist das ziemlich aufwändig. Da ist es wahrscheinlich einfacher, für die CLR-Typen überladene Versionen von ReadSetting bereit zu stellen, als jedes Mal einen solchen Struct anzulegen.

Weitere Möglichkeit ist sowas hier:
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:
23:
        public T ReadSetting<T>(string settingName, T def)                        
        {
            if (this[settingName] == "")
            {                              
                return def;
            }

            Type t = typeof(T);

            MethodInfo mi;
            if (methodinfos.ContainsKey(t))
                mi = methodinfos[t];
            else
            {
                mi = t.GetMethod("Parse");
                methodinfos[t] = mi;
            }

            if (mi != null)
                return (T)mi.Invoke(t, new object[] { this[settingName] });

            return def;
        }
Da habe ich ein kleines Caching eingebaut, um das Lesen der Settings via Reflection etwas schneller zu gestalten. Wieviel das bringt, kann ich leider nicht sagen.

Grüße
Christian

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
Robert_G
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 416


Delphi32 (D2005 PE); Chrome/C# (VS2003 E/A, VS2005)
BeitragVerfasst: So 05.03.06 16:40 
Ich war so frei und habe gerade eine Klasse geschrieben, welche eine dynamic method benutzt.
Da generische Klassen einmal pro Typensatz geladen werden, reichte es eine statische Variable für das delegate zu halten. Dieses delegate enthält einen Zeiger auf eine Methode, die dynamisch eine weitere Methode generiert (3 Zeilen IL Code ;) ) und dem statischen delegate zuweist.
Auf die Art wird beim nächten Ausführen die neue Methode benutzt und man spart sich irgendwelche if clauses.
Delegates sind in 2.0 sackschnell geworden, diese Lösung sollte theoretich schneller als ein virtual call oder ein interface call sein!

Ups coe vergessen :lol: :
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
public static class Converter<T>
{
  static Converter<string, T> conversionHandler = InitialHandler;

  static T InitialHandler(string input)
  {
    conversionHandler = BuildConversionMethod();
    return conversionHandler(input);
  }

  static Converter<string, T> BuildConversionMethod()
  {
    Type returnType = typeof(T);
    
    Type[] parseMethodArgs = { typeof(string) };
    MethodInfo parseMethod = returnType.GetMethod("Parse", parseMethodArgs);

    if (parseMethod == null)
      throw new InvalidOperationException(string.Format("{0} does not have an accessible static Parse method.",
                                returnType));
    Type[] finalMethodArgs = { typeof(string) };

    DynamicMethod finalMethod = new DynamicMethod("ConvertTo" + returnType.Name,
                            returnType,
                            finalMethodArgs,
                            parseMethod.DeclaringType);

    ILGenerator codeGen = finalMethod.GetILGenerator(256);

    codeGen.Emit(OpCodes.Ldarg_0);
    codeGen.EmitCall(OpCodes.Call, parseMethod, null);
    codeGen.Emit(OpCodes.Ret);

    finalMethod.DefineParameter(1,ParameterAttributes.In,"inputValue");
    
    return (Converter<string, T>)finalMethod.CreateDelegate(typeof(Converter<string, T>));
  }

  public static T Convert(string input)
  {
    return conversionHandler(input);
  }
}
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: So 05.03.06 16:47 
:shock: Das muss ich mir jetzt erst einmal in Ruhe angucken. Danke!

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
Robert_G
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 416


Delphi32 (D2005 PE); Chrome/C# (VS2003 E/A, VS2005)
BeitragVerfasst: So 05.03.06 16:53 
user profile iconChristian S. hat folgendes geschrieben:
:shock: Das muss ich mir jetzt erst einmal in Ruhe angucken. Danke!
Ist eigentlich ziemlich easy.
Der IL Generator nimmt eine MethodInfo und wandelt diese in einen richtigen call um. So kann man die nette Reflection benutzen.
Der IL Code selbst wirft nur den string parameter in den stack, führt die methode aus (null, weil keine instanz benutzt wird) und springt mit einem Return raus. Das Ergebnis der Methode ist ja das letzte was in den Stack kam. Wer etwas ASM kann, kommt damit sofort klar. ;)
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: So 05.03.06 17:04 
Habe zwar noch nie mit Assembler gearbeitet, aber ich denke, habe es trotzdem kapiert ;-)

Wirklich schickes Stück Code! Vielen Dank!

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: So 05.03.06 18:29 
Hallo!

Die Klasse funktionierte leider nicht, wenn es um Einstellungen ging, die tatsächlich ein String waren und daher nicht geparst werden mussten. Ich habe daher folgende Anpassungen vorgenommen, evtl. fällt Dir ja noch was Besseres ein, für mich ist das irgendwie noch zu neu :?

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:
23:
24:
25:
26:
27:
28:
29:
30:
        static Converter<string, T> BuildConversionMethod()
        {
            Type returnType = typeof(T);

            Type[] parseMethodArgs = { typeof(string) };
            MethodInfo parseMethod = returnType.GetMethod("Parse", parseMethodArgs);

            if (parseMethod == null && returnType != typeof(string))
                throw new InvalidOperationException(string.Format("{0} does not have an accessible static Parse method.",
                                          returnType));

            Type[] finalMethodArgs = { typeof(string) };

            Type decType = (returnType != typeof(string)) ? parseMethod.DeclaringType : typeof(string);
            DynamicMethod finalMethod = new DynamicMethod("ConvertTo" + returnType.Name,
                                    returnType,
                                    finalMethodArgs,
                                    decType);

            ILGenerator codeGen = finalMethod.GetILGenerator(256);

            codeGen.Emit(OpCodes.Ldarg_0); 
            if (returnType != typeof(string))
                codeGen.EmitCall(OpCodes.Call, parseMethod, null);
            codeGen.Emit(OpCodes.Ret);

            finalMethod.DefineParameter(1, ParameterAttributes.In, "inputValue");

            return (Converter<string, T>)finalMethod.CreateDelegate(typeof(Converter<string, T>));
        }

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
Robert_G
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 416


Delphi32 (D2005 PE); Chrome/C# (VS2003 E/A, VS2005)
BeitragVerfasst: So 05.03.06 19:05 
Ich habe zwar gedacht, du würdest einen String in deinem ReadSettings speziell behandeln, aber wenn es so ist...
Was hälst du hiervon?
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
public static class StringConverter<T>
{
  static Converter<string, T> conversionHandler = InitialHandler;

  static T InitialHandler(string input)
  {
    conversionHandler = BuildConversionMethod();
    return conversionHandler(input);
  }

  static Converter<string, T> BuildConversionMethod()
  {
    Type returnType = typeof(T);
    Type stringType = typeof(string);
    Type[] finalMethodArgs = { stringType };

    DynamicMethod finalMethod = new DynamicMethod("ConvertTo" + returnType.Name,
                            returnType,
                            finalMethodArgs,
                            returnType);

    ILGenerator codeGen = finalMethod.GetILGenerator(256);

    codeGen.Emit(OpCodes.Ldarg_0);

    if (returnType != stringType)
      BuildParseMethodCall(codeGen);

    codeGen.Emit(OpCodes.Ret);

    finalMethod.DefineParameter(1, ParameterAttributes.In, "inputValue");

    return (Converter<string, T>)finalMethod.CreateDelegate(typeof(Converter<string, T>));
  }

  static void BuildParseMethodCall(ILGenerator codeGen)
  {
    Type returnType = typeof(T);
    Type[] parseMethodArgs = { typeof(string) };
    MethodInfo parseMethod = returnType.GetMethod("Parse", parseMethodArgs);

    if (parseMethod == null)
      throw new InvalidOperationException(string.Format("{0} does not have an accessible static Parse method.",
                                returnType));
    codeGen.EmitCall(OpCodes.Call, parseMethod, null);
  }

  public static T Convert(string input)
  {
    return conversionHandler(input);
  }
}
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: So 05.03.06 19:16 
Japp, so sieht es sauberer aus :-)

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
thomas_danecker
Hält's aus hier
Beiträge: 2



BeitragVerfasst: Di 14.03.06 20:21 
Titel: XML Serialization
Wie wärs wenn du die Einstellungen einfach als XML serialisierst?

XML wird von .net prima unterstützt und es ist wirklich kinderleicht Konfigurationen damit zu speichern.

Ein kleines Beispiel:
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:
23:
24:
25:
26:
class Configuration
{
    private int aNumber;
    private string aString;
    private double aDouble;

    public int ANumber
    {
        get { return aNumber; }
        set { aNumber = value; }
    }
    public string AString
    {
        get { return aString; }
        set { aString = value; }
    }
    public double ADouble
    {
        get { return aDouble; }
        set { aDouble = value; }
    }

    public Configuration()
    {
    }
}

Diese Klasse repräsentiert deine Einstellungen

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
using System.Xml;
using System.Xml.Serialization;

...

XmlSerializer serializer = new XmlSerializer(typeof(Configuration));
...
Configuration config = ...
...
serializer.Serialize(stream, config);
...
config = (Configuration)serializer.Deserialize(stream);

Mit dem XmlSerializer aus dem Namespace System.Xml.Serialization kannst du deine Konfigurations-Objekte dann ganz einfach in Dateien (oder jede beliebige Art von Streams) schreiben oder lesen.
Du kannst mit Attributen (auch aus dem System.Xml.Serialization Namespace) auch noch ganz exakt regeln wie die Objekte serialisiert werden sollten.


Ich finde das wäre die eleganteste Lösung für dein Problem.
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Di 14.03.06 20:25 
Nein, ist leider keine Lösung. Die Einstellungen liegen nicht in Form einer einzigen Klasse vor. Können sie auch nicht, weil Plugins zur Laufzeit ihre eigenen Einstellungen hinzufügen können.

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".