Laden...

Rückgabetyp = Typ der aktuellen Klasse: ohne Generics?

Erstellt von Gandi vor 13 Jahren Letzter Beitrag vor 13 Jahren 3.295 Views
G
Gandi Themenstarter:in
7 Beiträge seit 2010
vor 13 Jahren
Rückgabetyp = Typ der aktuellen Klasse: ohne Generics?

Ich weiß, bescheuerter Threadname.

Jedenfalls hätte ich mal eine kurze Frage: Gibt es einen eleganteren Weg, um das hier zu erreichen?


public class Base
{
    public Base()
    {
    }

    public static virtual T Create<T>() where T : Base, new()
    {
        if (SomeCondition())
            return new T();
        
        return null;
    }
}

public class A : Base
{ 
}

public class B : Base
{
}

public class C : Base
{
}

public class Program
{
    public static void Main()
    {
        A x = A.Create<A>(); // <- Hier möchte ich das <A> weglassen können, es soll immer das selbe tun nur halt ein A returnen
        B x2 = B.Create<B>(); // <- Hier möchte ich das <B> weglassen können, es soll immer das selbe tun nur halt ein B returnen
    }
}

Ich könnte die Create-Methode natürlich in jeder Klasse überschreiben, aber das wäre ja auch unschön.

Gibt es also einen besseren Weg, so etwas zu erreichen?

2.187 Beiträge seit 2005
vor 13 Jahren

Hallo Gandi,

Warum machst du das mit einer statischen Mehtode?
Du solltest dir hierfür lieber das Factory-Pattern (oder AbstractFactory-Pattern) ansehen.

Gruß
Juy Juka

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,


public static virtual T Create<T>()

Lässt sich schon mal gar nicht kompilieren. Entweder static oder virtual.

Was stört dich denn am generischen Typen? Ich finde das so eh elegant 😉

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

G
Gandi Themenstarter:in
7 Beiträge seit 2010
vor 13 Jahren

Wow, das geht in diesem Forum aber verdammt schnell mit Antworten 👍

Lässt sich schon mal gar nicht kompilieren. Entweder static oder virtual.

Ups, das kommt davon wenn man sich in ner Minute schnell einen Beispielcode zusammenbastelt^^ Das "virtual" gehört da natürlich nicht hin.

Was stört dich denn am generischen Typen? Ich finde das so eh elegant 😉

Ja, ist schon gar nicht so übel (ich finde es zumindest schöner als das Factory-Pattern das JuyJuka vorgeschlagen hat), aber ich hatte gehofft dass mich C# auch hier nicht enttäuschen würde und man das noch schöner lösen kann 😉

1.378 Beiträge seit 2006
vor 13 Jahren

Nicht schöner aber anders...


public class A : Base
{
   public static A Create(){  }
}

public class B : Base
{
   public static B Create(){  }
}

public class C : Base
{
   public static C Create(){  }
}

public class Program
{
    public static void Main()
    {
        A x = A.Create();
        B x2 = B.Create();
    }
}

Lg XXX

3.170 Beiträge seit 2006
vor 13 Jahren

Hallo,

diese Variante ist zwar auch generisch, hat aber den Vorteil, daß das Typargument nur in der Klassendeklaration, nicht aber im Aufruf verwendet werden muss:

public class Base<T> where T : Base<T>, new()
{
    public Base()
    {
    }

    public static T Create()
    {
        if (SomeCondition())
            return new T();

        return null;
    }
}

public class A : Base<A> // <- <A> nur bei Klassendeklaration nötig
{
}

public class B : Base<B> // <- <B> nur bei Klassendeklaration nötig
{
}

public class C : Base<C>
{
}

public class Program
{
    public static void Main()
    {
        A x = A.Create(); // <- kein <A> mehr nötig
        B x2 = B.Create(); // <- kein <B> mehr nötig
    }
}

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

N
46 Beiträge seit 2007
vor 13 Jahren

Hallo,

gibt es für soetwas irgendeinen praktischen Nutzen? Tschuldigung, dass ich nachfrage. Aber für mich sieht das irgendwie so aus, als ob Du C# eine Delphi-Syntax aufzwingen willst...

Grüsse,

N1ls

G
Gandi Themenstarter:in
7 Beiträge seit 2010
vor 13 Jahren

@MarsStein: Das sieht schonmal gar nicht schlecht aus, ich wusste gar nicht dass man die Klasse selbst in den Generic-Parametern benutzen kann... wieder was gelernt 😃

@N1ls: Was genau meinst du? Das mit dem statischen "Create" oder dass die Syntax dafür möglichst kurz sein soll? Für das "Create" habe ich jedenfalls einen praktischen Nutzen, ist allerdings etwas aufwändiger zum erklären. Und Delphi kann ich auf jeden Fall mal nicht^^

R
103 Beiträge seit 2009
vor 13 Jahren

Hmm ganz nett, aber auch beim letzten Ansatz von MarsStein hast Du das Problem dass du einen public Konstruktor in den Klasssen A,B ,C haben musst.
Deshalb hält es keinen unwissenden Programmierer davon ab einfach

a x = new A();

zu schreiben und das wars dann mit der netten Create() Methode.
Ich würde hier eher zu Interfaces tendieren und wenn Du schon eine extra Create Methode hast die Konstruktoren auf protected aetzen, damit keiner new() verwenden kann.

3.170 Beiträge seit 2006
vor 13 Jahren

Hallo,

Hmm ganz nett, aber auch beim letzten Ansatz von MarsStein hast Du das Problem dass du einen public Konstruktor in den Klasssen A,B ,C haben musst.

Richtig. Aufgrund von where T: new() muss ein öffentlicher, parameterloser Konstruktor vorliegen.
Das ist bei der statischen Methode im Startbeitrag allerdings auch nicht anders, weshalb ich davon ausging, daß das kein Problem darstellt. Man bekommt halt keinen Zugriffsschutz, kann aber trotzdem mit der Create-Methode die Objekte in Abhängigkeit von internen Faktoren erstellen.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

R
103 Beiträge seit 2009
vor 13 Jahren

Hmm, habe mal das Beispiel von MarsStein etwas umgebaut. So würde es auch gehen

public class Base<T> where T : Base<T>
    {
        protected Base()
        {
        }

        public static T Create()
        {
            if (SomeCondition())
                return (T) Activator.CreateInstance(typeof(T),true);
            return null;
        }
    }

    public class A : Base<A> // <- <A> nur bei Klassendeklaration nötig
    {
        protected A()
        {
        }
    }

So kann man kein

A = new A() 

schhreiben, sondern muss auf A.Create() ausweichen...

3.170 Beiträge seit 2006
vor 13 Jahren

Hallo rasepretrep,

ist zwar etwas besser, verschiebt aber nur das Problem:
So ist keiner mehr gezwungen, einen parameterlosen Konstruktor für erbende Klassen zu erstellen. Wenn's keinen gibt, kommt die Exception dann erst zur Laufzeit.


// so gibt's den Kracher:
    public class A : Base<A> // <- <A> nur bei Klassendeklaration nötig
    {
        protected A(string initStr)
        {
        }
    }

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

R
103 Beiträge seit 2009
vor 13 Jahren

Stimmt, hast recht. Schade dass man nicht sowas wie

public class Base<T> where T : Base<T>, protected new()

schreiben kann 😉

S
443 Beiträge seit 2008
vor 13 Jahren

Schuss in den Nebel:

public abstract class Base<T> where T : Base<T>
{
    public Base()
    {
    }

    public static T Create()
    {
        if (SomeCondition())
            return CreateInstance();

        return null;
    }

    protected abstract T CreateInstance();

}

public class A : Base<A> // <- <A> nur bei Klassendeklaration nötig
{
  protected override A CreateInstance()
  {
    return new A();
  }
}

public class B : Base<B> // <- <B> nur bei Klassendeklaration nötig
{
  protected override B CreateInstance()
  {
    return new B();
  }
}

public class C : Base<C>
{
  protected override B CreateInstance()
  {
    return new B();
  }
}

public class Program
{
    public static void Main()
    {
        A x = A.Create(); // <- kein <A> mehr nötig
        B x2 = B.Create(); // <- kein <B> mehr nötig
    }
} 

// EDIT Copyright MarsStein

mbg
Rossegger Robert
mehr fragen mehr wissen

Montag morgen ist die beste Zeit um eine erfolgreiche Woche zu beginnen

3.170 Beiträge seit 2006
vor 13 Jahren

Hallo spike24,

das geht so leider gar nicht.
Du kannst CreateInstance nicht in Create aufrufen, weil sie nicht static ist.
Du bräuchtest also zuerst eine Instanz, um die Methode aufzurufen - wsa in dem Zusammenhang natürlich unsinnig ist.

    public static T Create()
    {
        if (SomeCondition())
            return CreateInstance(); // <-- hier bräuchtest Du eine Instanz, um die Methode aufzurufen.

        return null;
    }

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

S
443 Beiträge seit 2008
vor 13 Jahren

oh, static, übelesen

    public interface ICreateable<T> where T : Base<T>
    {
      T CreateInstance();
    }

    public abstract class Base<T> where T : Base<T>
    {

      private static Dictionary<Type, object> mFactories;

      static Base()
      {
        mFactories = new Dictionary<Type, object>();
        mFactories.Add(typeof(A), new A());
        mFactories.Add(typeof(B), new B());
        mFactories.Add(typeof(C), new C());
      }

      public Base()
      {
      }

      public static T Create()
      {
        if (SomeCondition())
        {
          ICreateable<T> factory = (ICreateable<T>)mFactories[typeof(T)];
          return factory.CreateInstance();
        }

        return null;
      }

    }

    public class A : Base<A>, ICreateable<A> // <- <A> nur bei Klassendeklaration nötig
    {
      public A CreateInstance()
      {
        return new A();
      }
    }

    public class B : Base<B>, ICreateable<B> // <- <B> nur bei Klassendeklaration nötig
    {
      public B CreateInstance()
      {
        return new B();
      }
    }

    public class C : Base<C>, ICreateable<C>
    {
      public C CreateInstance()
      {
        return new C();
      }
    }

aber von schön sind wir nun auch schon ein Stück weit entfernt.

//Edit: man sollte builden bevor man postet
// Edit 2: Ups, sorry!

mbg
Rossegger Robert
mehr fragen mehr wissen

Montag morgen ist die beste Zeit um eine erfolgreiche Woche zu beginnen

3.170 Beiträge seit 2006
vor 13 Jahren

Hallo,

probier doch Deinen Code bitte mal aus bevor Du ihn postest. Soll ich das jetzt etwa korrigieren?
Und was willst Du mit dem Finalizer??

~Base()

Gruß, MarsStein

EDIT: OK danke für die Korrektur. Das Unschöne an der Lösung besteht darin, daß Deine Basisklasse im statischen Konstruktor alle Kindklassen kennen muss. Aber bevor Du jetzt anfängst, eine statische Methode zu basteln, mit der sich die Kindklassen registrieren sollen - gefragt war nach etwas eleganterem als der Code im Startbeitrag.

Außerdem sind jetzt auch die Konstruktoren wieder public, damit besteht kein Vorteil mehr zu meinem ursprünglichen Vorschlag - wobei sie bei Dir internal sein könnten..

Zusätzlich ist die CreateInstance-Methode public und muss es wegen dem Interface auch bleiben - mehr Schutz gibt's da also auch nicht.

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

N
46 Beiträge seit 2007
vor 13 Jahren

@N1ls: Was genau meinst du? Das mit dem statischen "Create" oder dass die Syntax dafür möglichst kurz sein soll? Für das "Create" habe ich jedenfalls einen praktischen Nutzen, ist allerdings etwas aufwändiger zum erklären. Und Delphi kann ich auf jeden Fall mal nicht^^

Ich meine eher das statische Create. Kurz wird's ja schon, wenn ich nur


A myA = new A()

schreibe.

Konstruktoren sind eh statisch. Also wieso diese Umständlichkeiten?

Grüsse,

N1ls

S
443 Beiträge seit 2008
vor 13 Jahren

Konstruktoren sind eh statisch? ähm ... nein
Es gibt static und instance Konstruktoren
Die statischen haben static davor stehen die anderen einen Accessmodifier (public, protected, internal, private)

@MarsStein
Ja, schön ist das nicht sonderlich (in meiner benutzung dieses constructs lese ich eine Assembly aus und befülle somit das Dictionary mit den elementen ohne sie zu kennen)
Ja die construktoren können internal sein, ging mir nur ums Prinzip.

//Edit, mich hats nicht in Ruhe gelassen
jetzt gibt es nur noch die unsauberkeit des statischen ctors, was ich bei mir ja anders gelöst habe.

  internal interface ICreateable<T> where T : Base<T>
  {
    T CreateInstance();
  }

  public abstract class Base<T> where T : Base<T>
  {

    private static Dictionary<Type, object> mFactories;

    static Base()
    {
      mFactories = new Dictionary<Type, object>();
      mFactories.Add(typeof(A), new A());
      mFactories.Add(typeof(B), new B());
      mFactories.Add(typeof(C), new C());
    }

    public Base()
    {
    }

    public static T Create()
    {
      if (SomeCondition())
      {
        ICreateable<T> factory = (ICreateable<T>)mFactories[typeof(T)];
        return factory.CreateInstance();
      }

      return null;
    }

    private static bool SomeCondition()
    {
      return true;
    }

  }

  public class A : Base<A>, ICreateable<A> // <- <A> nur bei Klassendeklaration nötig
  {

    internal A()
    { }

    A ICreateable<A>.CreateInstance()
    {
      return new A();
    }

  }

  public class B : Base<B>, ICreateable<B> // <- <B> nur bei Klassendeklaration nötig
  {
    internal B()
    { }

    B ICreateable<B>.CreateInstance()
    {
      return new B();
    }

  }

  public class C : Base<C>, ICreateable<C>
  {
    internal C()
    { }

    C ICreateable<C>.CreateInstance()
    {
      return new C();
    }

  }

mbg
Rossegger Robert
mehr fragen mehr wissen

Montag morgen ist die beste Zeit um eine erfolgreiche Woche zu beginnen

S
417 Beiträge seit 2008
vor 13 Jahren

jetzt gibt es nur noch die unsauberkeit des statischen ctors, was ich bei mir ja anders gelöst habe.

Naja, dass die Basisklasse ihre abgeleiteten Klassen kennt, finde ich persönlich auch etwas "unsauber".

S
443 Beiträge seit 2008
vor 13 Jahren

das meinte ich, schlecht geschrieben.

Könnte man aber über Reflection lösen.
Allerdings müssten dann die abgeleiteten Klassen in der selben Assembly liegen, sonst können sie keinen internal-ctor haben ... ausser man verwendet das Attribut InternalsVisibleToAttribute.

mbg
Rossegger Robert
mehr fragen mehr wissen

Montag morgen ist die beste Zeit um eine erfolgreiche Woche zu beginnen