Laden...

[gelöst] Generics: Abstrakte Klassen und IEnumerable

Erstellt von Alan vor 10 Jahren Letzter Beitrag vor 10 Jahren 2.903 Views
A
Alan Themenstarter:in
22 Beiträge seit 2013
vor 10 Jahren
[gelöst] Generics: Abstrakte Klassen und IEnumerable

Hallo zusammen,

ich versuche im Moment, eine abstrakte Basisklasse zu erstellen, die einige gemeinsame Operationen für alle abgeleiteten Klassen zur Verfügung stellen soll (bisher handelte es sich hierbei noch um ein Interface). Die Signatur der Klasse sieht grob gesagt so aus:

public abstract class MyAbstractBaseClass : IEnumerable 

Die nicht-generische Variante von IEnumerable braucht man in C# meines Wissens nur, weil die Sprache keine generischen Wildcards (wie z.B. <?> in Java) unterstützt. Daher gibt es nun auch die generische Variante:

public abstract class MyAbstractBaseClass<T> : MyAbstractBaseClass, IEnumerable<T>

Zu beachten ist hierbei, dass die generisch-abstrakte Klasse von der nicht-generischen Variante erbt und eben zusätzlich das generische IEnumerable implementiert.

Jetzt habe ich allerdings ein Problem mit der "GetEnumerator"-Methode in MyAbstractBaseClass<T>. Die nicht-generische Methode soll von dieser Klasse wie folgt implementiert werden:

IEnumerator IEnumerable.GetEnumerator(){
   // weiterleitung zur generischen Methode, da IEnumerable<T> das Interface IEnumerable implementiert
   return this.GetEnumerator()
}

... und die generische Variante soll sein:


// Variante 1: Compile Error: Return-Typ stimmt nicht mit IEnumerable.GetEnumerator() überein
public abstract IEnumerator<T> GetEnumerator();
// Variante 2: Compile Error: Explizite Interface-Implementierungen dürfen nicht abstract sein
public abstract IEnumerator<T> IEnumerable<T>.GetEnumerator();

Aber leider schluckt der Compiler das nicht. Man kann scheinbar eine explizite Interface-Implementierung nicht als "abstract" deklarieren - und da Ergebnistypen von Methoden in C# nicht kovariant sind (das würde das gesamte Problem ohnehin lösen), stellt sich nun die Frage: wie macht man das? Auch Tricks mit "override" und "new" helfen hier scheinbar nicht (ich fange langsam an, die Type Erasure aus Java zu vermissen - hätte ich auch nie gedacht).

Mein Ziel wäre es, dass die von MyAbstractBaseClass<T> abgeleiteten Klassen nur noch die generische GetEnumerator()-Methode implementieren müssen (die nicht-generische leitet in der abstrakten Klasse auf die generische Variante weiter) und alle von MyAbstractBaseClass abgeleiteten Klassen ausschließlich die nicht-generische GetEnumerator()-Methode. Aber das bekomme ich syntaktisch nicht so hin, dass es der Compiler auch versteht.

Falls das nicht möglich sein sollte, wäre es mir alternativ auch recht, wenn die generischen Subklassen eben beide GetEnumerator()-Methoden selbst implementieren müssen - das wäre auch kein Beinbruch. Aber wie stellt man das in der abstrakten Basis-Klasse dann syntaktisch an?

Sorry falls diese Frage ziemlich "noob-ish" ist, aber die Generics funktionieren in C# wirklich wesentlich anders als in Java.

Danke,

Alan

EDIT: Ich bin gerade durch Zufall auf eine plausible Lösung gestoßen, und zwar in der letzten Antwort in der Thread abstract explicit interface implementation in C# auf StackOverflow. Der Trick besteht darin, dass man in den abstrakten Klassen so weit oben in der Hierarchie wie möglich die von den Interfaces verlangten Methoden explizit implementiert, und dabei dann aber auf abstrakte Methoden verweist, die einen etwas anderen Namen haben. So erspart man sich viel Kopfzerbrechen und muss in den abgeleiteten Klassen nur noch das Notwendigste implementieren.

M
171 Beiträge seit 2012
vor 10 Jahren

Falls ich Dich richtig verstanden habe (bin nicht ganz sicher) - ist es das was Du suchst ?


 public abstract class MyAbstractBaseClass : IEnumerable
    {
        public IEnumerator GetEnumerator()
        {
            //Implementierung
            return null;
        }
    }

    public abstract class MyAbstractBaseClass<T> : MyAbstractBaseClass, IEnumerable<T>
    {
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        public new abstract IEnumerator<T> GetEnumerator();        
    }

A
Alan Themenstarter:in
22 Beiträge seit 2013
vor 10 Jahren

@Mallet: Danke für deine Antwort, leider gerade eine Minute zu spät 😃 Siehe mein "Edit" des Start-Posts ganz am Ende _

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Alan,

also ich verstehe das Problem nicht. Bei mir geht das problemlos (direkt und ohne Umwege):


using System;
using System.Collections;
using System.Collections.Generic;

public abstract class MyAbstractBaseClass : IEnumerable
{
   IEnumerator IEnumerable.GetEnumerator ()
   {
      Console.WriteLine ("   MyAbstractBaseClass.IEnumerable.GetEnumerator ()");
      // Kann nicht zur generischen Methode weiterleiten, weil T nicht bekannt ist.
      // Wird aber sowieso nie aufgerufen.
      // Zumindest wenn es keine konkreten Unterklassen von MyAbstractBaseClass gibt,
      // die nicht gleichzeitig auch Unterklassen von MyAbstractBaseClass<T> sind.
      // Könnte auch eine NotImplementedException werfen.
      return null;
   }
}

public abstract class MyAbstractBaseClass<T> : MyAbstractBaseClass, IEnumerable<T>
{
   IEnumerator IEnumerable.GetEnumerator ()
   {
      Console.WriteLine ("   MyAbstractBaseClass<T>.IEnumerable.GetEnumerator ()");
      // Weiterleitung zur generischen Methode.
      // Funktioniert, weil IEnumerable<T> das Interface IEnumerable implementiert
      // und weil die (abstrakte) generische GetEnumerator-Methode hier bekannt ist.
      return this.GetEnumerator ();
   }

   public abstract IEnumerator<T> GetEnumerator ();
}

public class MySubClass<T> : MyAbstractBaseClass<T>
{
   public override IEnumerator<T> GetEnumerator ()
   {
      Console.WriteLine ("   MySubClass<T>.GetEnumerator ()");
      // Hier die konkrete Implementierung einsetzen.
      return null;
   }
}

static class App
{
   public static void Main (string [] astrArg)
   {
      MyAbstractBaseClass         au = new MySubClass<String> ();
      MyAbstractBaseClass<String> ag = new MySubClass<String> ();
      MySubClass<String>          sg = new MySubClass<String> ();

      // au.GetEnumerator (); // Nicht möglich, weil GetEnumerator für au nur interface-explizit implementiert.
      Console.WriteLine ("((IEnumerable)au).GetEnumerator ();");
      ((IEnumerable)au).GetEnumerator ();
      Console.WriteLine ("((IEnumerable<String>)au).GetEnumerator ();");
      ((IEnumerable<String>)au).GetEnumerator ();

      Console.WriteLine ("ag.GetEnumerator ();");
      ag.GetEnumerator ();
      Console.WriteLine ("((IEnumerable)ag).GetEnumerator ();");
      ((IEnumerable)ag).GetEnumerator ();
      Console.WriteLine ("((IEnumerable<String>)ag).GetEnumerator ();");
      ((IEnumerable<String>)ag).GetEnumerator ();

      Console.WriteLine ("sg.GetEnumerator ();");
      sg.GetEnumerator ();
      Console.WriteLine ("((IEnumerable)sg).GetEnumerator ();");
      ((IEnumerable)sg).GetEnumerator ();
      Console.WriteLine ("((IEnumerable<String>)sg).GetEnumerator ();");
      ((IEnumerable<String>)sg).GetEnumerator ();
   }
}

Ausgabe:

[pre]
((IEnumerable)au).GetEnumerator ();
   MyAbstractBaseClass<T>.IEnumerable.GetEnumerator ()
   MySubClass<T>.GetEnumerator ()
((IEnumerable<String>)au).GetEnumerator ();
   MySubClass<T>.GetEnumerator ()
ag.GetEnumerator ();
   MySubClass<T>.GetEnumerator ()
((IEnumerable)ag).GetEnumerator ();
   MyAbstractBaseClass<T>.IEnumerable.GetEnumerator ()
   MySubClass<T>.GetEnumerator ()
((IEnumerable<String>)ag).GetEnumerator ();
   MySubClass<T>.GetEnumerator ()
sg.GetEnumerator ();
   MySubClass<T>.GetEnumerator ()
((IEnumerable)sg).GetEnumerator ();
   MyAbstractBaseClass<T>.IEnumerable.GetEnumerator ()
   MySubClass<T>.GetEnumerator ()
((IEnumerable<String>)sg).GetEnumerator ();
   MySubClass<T>.GetEnumerator ()
[/pre]

herbivore

A
Alan Themenstarter:in
22 Beiträge seit 2013
vor 10 Jahren

Hi herbivore,

ehrlich gesagt: ich verstehe es auch nicht - genau das Szenario das du hier beschreibst, wollte bei mir gestern partout nicht compilen! Ich glaube ich hab gestern einfach zu viel Code geschrieben und dann irgendwo (evtl. bei der Basis-Typ-Deklaration) einen blöden Fehler eingebaut, anders ist das kaum zu erklären.

Danke,

Alan