Autor Beitrag
r2c2
ontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic starofftopic star
Beiträge: 324
Erhaltene Danke: 2

Linux

BeitragVerfasst: Do 23.03.06 21:49 
Hallo :wave:
ich suche ne C#-Entsprechung(.NET 2.0) für
ausblenden Delphi-Quelltext
1:
2:
type
  Bar = class of Foo;

Also so ne Art typisierter Klassenzeiger. Gibts sowas oder muss ich mir, wenn ich sowas brauch, alle Infos per Reflection holen?

Kurz zur Info, was ich machen will:
Ich hab vor so ne Art dynamische Factory in C# zu basteln. Grundlage bilden diese zwei Threads:
- www.delphi-library.d...41404&highlight=
- www.delphi-library.d...ellschoen_35434.html

//Edit: Links verbessert...

mfg

Christian

_________________
Kaum macht man's richtig, schon klappts!
Robert_G
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 416


Delphi32 (D2005 PE); Chrome/C# (VS2003 E/A, VS2005)
BeitragVerfasst: Fr 24.03.06 00:21 
nope, MetaClasses sind kein Teil der CLR. Somit gibt's die auch nicht in C#. :(

Wenn es dir nur darum geht Instanzen zu erzeugen wäre dass hier möglich:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
T CreateInstance<T>()
  where T : new()
{
  return new T();
}


Man beachte die new() -constraint, welche für T einen öffentlichen, parameterlosen Contructor vorschreibt.

Willst du dem Constructor Parameter übergeben hast du die Qual der Wahl...
Es gibt da die Klasse Activator. Die it einfach zu benutzen, aber muss für jeden Call den richtigen Constructor finde -> laaaahmarschig :(

Du kannst dir eine generische Factory bauen, die den nötigen Il Code für einen call auf den richtigen Constructor generiert.
Da generische Klassen für jeden unterchiedlichen Typparameter einen eigenen Satz von statischen Variablen haben, ist es sehr easy den genierten Code immer wieder benutzen zu können.
Da ich gerne mit Dynamic Methods spiele, habe ich dir mal ein kleines Beispiel gebaut.
Die Methode zum Generieren des constructor Wrappers habe ich absichtlich unabhängig von Parameteranzahl und -typ gemacht, so kannst du sie einfach selbst benutzen. :)


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:
static class Factory<T>
{
  public static T CreateInstance(int someValue)
  {
    return constructor(someValue);
  }

  // der delegate type
  delegate T Constructor(int someValue);

  static Constructor constructor = InitialConstructor;

  // beim ersten Zugriff wird die Methode erzeugt
  static T InitialConstructor(int someValue)
  {
    // generiere den Wrapper und biege constructor dahin
    constructor = CreateConstructorCall(new Type[] { typeof(int) });

    return constructor(someValue);
  }

  static Constructor CreateConstructorCall(Type[] parameterTypes)
  {
    DynamicMethod method = new DynamicMethod("ConstructorCall",
                                             typeof(T),
                                             parameterTypes,
                                             typeof(Factory<T>));

    ILGenerator codeGen = method.GetILGenerator();


    // lade alle argumente auf den Stack
    for (byte argument = 0; argument < parameterTypes.Length; argument++)
    {
      codeGen.Emit(OpCodes.Ldarg_S, argument);
    }

    // finde den richtigen constructor 
    // und generiere einen passenden call
    codeGen.Emit(OpCodes.Newobj, typeof(T).GetConstructor(parameterTypes));
    // gebe die generierte Instanz vom Stack aus
    codeGen.Emit(OpCodes.Ret);

    return (Constructor)method.CreateDelegate(typeof(Constructor));
  }
}


Dynamic methods laufen unter "light weight code generations". D.h. der Code sollte im einstelligen ms-Bereich generiert werden.
Jeder weitere Call ist vergleichbar mit einem normal kompilierten statischen Methodenaufruf, auf deutsch: sackschnell. ;)


Hier noch ein bisschech Beispiel code, der zeigen soll wie und das es funktioniert.
Das Interface war nicht notwendig, aber dadurch muste ich die Test methode nur einmal schreiben. :zwinker:
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:
interface ISomeValue
{
  int SomeValue { get;}
}

struct Struct : ISomeValue
{
  int someValue;

  public int SomeValue
  {
    get { return someValue; }
  }

  public Struct(int someValue)
  {
    this.someValue = someValue;
  }
}

class Class : ISomeValue
{
  int someValue;

  public int SomeValue
  {
    get { return someValue; }
  }

  public Class(int someValue)
  {
    this.someValue = someValue;
  }
}

class Program
{
  static void Main(string[] args)
  {
    Test<Class>(1);
    Test<Struct>(2);
  }

  static void Test<T>(int someValue)
    where T : ISomeValue
  {
    T test = Factory<T>.CreateInstance(someValue);

    Console.WriteLine("{0}: {1}", test, test.SomeValue);
  }
}


Bei Fragen einfach fragen... :mrgreen:
r2c2 Threadstarter
ontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic starofftopic star
Beiträge: 324
Erhaltene Danke: 2

Linux

BeitragVerfasst: Fr 24.03.06 19:44 
user profile iconRobert_G hat folgendes geschrieben:
nope, MetaClasses sind kein Teil der CLR. Somit gibt's die auch nicht in C#. :(

Schade. :( Hoffentlich stoße ich nicht auf noch mehr Fehlende Feartures...

Zitat:

Willst du dem Constructor Parameter übergeben hast du die Qual der Wahl...

Jo, will ich. N string...

Zitat:

Du kannst dir eine generische Factory bauen, die den nötigen Il Code für einen call auf den richtigen Constructor generiert.
Da generische Klassen für jeden unterchiedlichen Typparameter einen eigenen Satz von statischen Variablen haben, ist es sehr easy den genierten Code immer wieder benutzen zu können.
Da ich gerne mit Dynamic Methods spiele, habe ich dir mal ein kleines Beispiel gebaut.
Die Methode zum Generieren des constructor Wrappers habe ich absichtlich unabhängig von Parameteranzahl und -typ gemacht, so kannst du sie einfach selbst benutzen. :)

Ui. :shock: Vielen Dank! :zustimm:

Zitat:

Bei Fragen einfach fragen... :mrgreen:

Jo, mach ich auch gleich mal. Hab mir deinen Code angeguckt. Hat zwar ne Zeit lang gedauert, aber jetzt hab ichs mehr oder weniger verstanden. Hab deinen Code mal rekommentiert und geschrieben, was IMHO da passiert.
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:
static class Factory<T>
{
  public static T CreateInstance(int someValue) // die Methode, die von außen aus betrachtet die Objekte erzeugt
  {
    return constructor(someValue); // gibt InitialConstructor per statischen Delegate zurück
  }

  // der delegate type
  delegate T Constructor(int someValue);

  static Constructor constructor = InitialConstructor; // hier wird dem statischen Delegate-Feld Contructor die Mthode InitialConstrucor zugewiesen

  // diese Methode erzeugt den Wrapper für den zu findenden constructor; die hast du nur gemacht, damit ich einfacher anpassen kann... Danke!
  static T InitialConstructor(int someValue)
  {
    // erzeugt n Wrapper für den zu findenden Constructor. Dieser muss die Parameter haben, die durch das Type[]-Array festgelegt sind
    constructor = CreateConstructorCall(new Type[] { typeof(int) });

    return constructor(someValue);
  }

      // diese Methode erzeugt den Call auf den constructor, der durch das Type[]-Array festgelegt ist
  static Constructor CreateConstructorCall(Type[] parameterTypes)
  {
            // erzeugt ne neue Mathode der Klasse Factory<T> mit dem Namen ContructorCall, dem Ruckgabewert T und den entsprechenden Parametern
    DynamicMethod method = new DynamicMethod("ConstructorCall",
                                             typeof(T),
                                             parameterTypes,
                                             typeof(Factory<T>));

    ILGenerator codeGen = method.GetILGenerator();


    // lade alle argumente auf den Stack; Das, was normalerweise automatisch passuiert muss man jetzt selber machen
    for (byte argument = 0; argument < parameterTypes.Length; argument++)
    {
      codeGen.Emit(OpCodes.Ldarg_S, argument);
    }

    // finde den richtigen constructor 
    // und generiere einen passenden call
    codeGen.Emit(OpCodes.Newobj, typeof(T).GetConstructor(parameterTypes));
    // gebe die generierte Instanz vom Stack aus; das macht OpCodes.Ret und codeGen.Emit führt das ganze aus
    codeGen.Emit(OpCodes.Ret);

            // delegiert die eben erzeugte Methode, bzw. deren Rückgabewert in den ReturnType als Constructor-Delegate
    return (Constructor)method.CreateDelegate(typeof(Constructor));
  }
}


Hab ich das so richtig verstanden?

mfg

Christian

P.S.: System.Reflection.Emit gefällt mir. Muss ich mir mal genauer angucken... :wink:

_________________
Kaum macht man's richtig, schon klappts!
Robert_G
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 416


Delphi32 (D2005 PE); Chrome/C# (VS2003 E/A, VS2005)
BeitragVerfasst: Fr 24.03.06 20:50 
user profile iconr2c2 hat folgendes geschrieben:
Hab ich das so richtig verstanden?
Ich fürchte nicht, aber vllt habe ich dich falsch verstanden. ;)
Ich nehme mal die wichtigsten Stellen und versuche es mal am Ablauf des ersten und zweiten Calls auf CreateInstance zu erklären. Vllt. hilft's dem nächsten Leser ja auch. :)

Wie du siehst ist CreateInstance selbst nur ein Wrapper um den Delegate.
ausblenden C#-Quelltext
1:
2:
3:
4:
public static T CreateInstance(int someValue)
{
  return constructor(someValue);
}


Der Delegate zeigt ersten Aufruf noch auf InitialConstructor:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
static Constructor constructor = InitialConstructor;

static T InitialConstructor(int someValue)
{
  constructor = CreateConstructorCall(new Type[] { typeof(int) });

  return constructor(someValue);
}

InitialConstructor hat die richtige Signatur, deshalb kann man ihn überhaupt dem Delegate zuweisen.
Er wird CreateConstructor aufrufen, damit dem Delegate eine fertig kompilierte, neue Methode zugewiesen wird.
Danach ruft er selbst den Delegate auf um den Wert zurückzugeben. Es soll ja nach außen keinen funktionialen Unterschied zwischen dem ersten und weiteren Calls auf CreateInstance geben. :zwinker:
Wie man in der ersten Zeile von InitialConstructor sieht zeigt der Delegate jetzt auf die neue Methode.

Jeder weitere Call auf CreateInstance wird also nicht mehr InitialConstructor sondern direkt die eben generierte Methode aufrufen.

Ich hätte es auch so schreiben können[meta]also ohne InitialConstructor[/meta]...
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
public static T CreateInstance(int someValue)
{
  if(constructor == null)
    constructor = CreateConstructorCall(new Type[] { typeof(int) });

  return constructor(someValue);
}
... aber ehrlich gesagt finde ich es wesentlich eleganter, den Delegate initial auf eine Methode zu setzen, die ihn vorbereitet und zurechtbiegt. Auf die Art spart man sich bei jedem weiteren Call einen Vergleich und einen Sprung[meta]keine if clause nötig[/meta]. :)


Zitat:
P.S.: System.Reflection.Emit gefällt mir. Muss ich mir mal genauer angucken... :wink:
Jupp, ist ziemlich cool. :)
r2c2 Threadstarter
ontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic starofftopic star
Beiträge: 324
Erhaltene Danke: 2

Linux

BeitragVerfasst: Sa 25.03.06 10:43 
Ah! Jetzt wird doch vieles klarer. Danke!

mfg

Christian

_________________
Kaum macht man's richtig, schon klappts!