Autor Beitrag
faux
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 36

Win XP Pro
C# (VS 2005), Delphi 7
BeitragVerfasst: Sa 07.01.06 01:29 
Hallo!

Ich implementiere das Interface IEnumerable in eine Klasse, dass ich diese per foreach durchlaufen kann.
Jedenfalls hätte ich das so gelöst:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
  public class Klasse1 : IEnumerable<string>
  {
    private string[] strings;
    private int ctr = 0;

    public IEnumerator<string> GetEnumerator()
    {
      foreach (string s in strings)
        yield return s;
    }
  }


Wenn ich das aber so belasse, gibts einen Error (einen, nicht zwei):
ausblenden Quelltext
1:
2:
'ConsoleApplication1.Klasse1' does not implement interface member 'System.Collections.IEnumerable.GetEnumerator()'.
'ConsoleApplication1.Klasse1.GetEnumerator()' is either static, not public, or has the wrong return type.


Wenn ich dann per F12 zur Deklaration des Interfaces springt, sehe ich den Grund:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
namespace System.Collections
{
  public interface IEnumerable
  {
    IEnumerator GetEnumerator();
  }
}


Klar, die Methode die mir das Interface vorschreibt verlangt also IEnumerator als Rückgabewert und nicht IEnumerator<string>.
Ich muss also meine Klasse um folgende Methode erweitern:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
    IEnumerator IEnumerable.GetEnumerator()
    {
      foreach (string s in strings)
        yield return s;
    }


Aber wieso das? Was hat das für einen Sinn? :?
foreach greift sowieso nur auf die implizite Methode zu.

Kann mir das jemand erklären?

Grüße
Faux
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 07.01.06 01:39 
Hallo!

Zur Verwendung von Iteratoren muss die Klasse nicht explizit IEnumerable implementieren. Es reicht, eine Methode mit der Signatur
ausblenden C#-Quelltext
1:
public IEnumerator GetEnumerator()					

zu implementieren (oder eine der genersichen Varianten, wie Du es hier möchtest). Wenn Du also das ": IEnumerable<string>" weglässt, sollte die eine Methode reichen.

So geht es auf jeden Fall bei mir:
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:
    class Program
    {
        static void Main(string[] args)
        {
            Klasse1 k = new Klasse1();
            foreach (string s in k)
                Console.WriteLine(s);

            Console.ReadLine();
        }
    }

    public class Klasse1
    {
        private string[] strings;
        private int ctr = 0;

        public Klasse1()
        {
            strings = new string[] { "foo""bar" };
        }

        public IEnumerator<string> GetEnumerator()
        {
            foreach (string s in strings)
                yield return s;
        }
    }


Grüße
Christian

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

Win XP Pro
C# (VS 2005), Delphi 7
BeitragVerfasst: Sa 07.01.06 01:41 
Ich habe mich gerade gefragt, wieso ich nicht einfach das <string> weglasse, also nur:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
  public class Klasse1 : IEnumerable
  {
    private string[] strings;
    private int ctr = 0;

    public IEnumerator GetEnumerator()
    {
      foreach (string s in strings)
        yield return s;
    }
  }


Dann funktioniert es ja genau so.
Ich habe das Beispiel aus einem C# Buch. Dort wird das mittels <string> gelößt. Also genau so, wie das aller erste Code-Segment meines ersten Posts. Wieso das?

NACHTRAG:
@Christian:
Es geht deswegen, weil du das Interface nicht implementierst. ;)

Grüße
Faux
Motzi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2931

XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
BeitragVerfasst: Sa 07.01.06 01:45 
Es ist ein Unterschied zwischen IEnumerable und IEnumerable<T>. Das eine Interface ist generisch, das andere nicht. Das generische Interface definiert eine Methode GetEnumerator() die wiederum ein generisches IEnumerator-Interface zurückgibt. Generische Interfaces sind typisiert und bieten daher eine besser Typsicherheit als das nicht generische Äquvalent. Wenn man sich aber mal die Deklaration des generischen IEnumerable anschaut sieht man folgendes:
ausblenden C#-Quelltext
1:
public interface IEnumerable<T> : IEnumerable					

Das generische Interface ist also vom nicht generischen abgeleitet. Man muss also folglich auch beide Interfaces implementieren. Da das generische Interface aber besere Typsicherheit bietet sollte dieses "normal" und das nicht generische implizit implementiert werden.

Gruß, Motzi

_________________
gringo pussy cats - eef i see you i will pull your tail out by eets roots!
faux Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 36

Win XP Pro
C# (VS 2005), Delphi 7
BeitragVerfasst: Sa 07.01.06 01:54 
Danke für die Antworten. Jetzt verstehe ich das ganze! ;)
Ist anscheinend ein fehler im Buch, das das nicht generische Interface nicht auch noch Implementiert wird.

Eine Frage noch:
user profile iconMotzi hat folgendes geschrieben:
Da das generische Interface aber besere Typsicherheit bietet sollte dieses "normal" und das nicht generische implizit implementiert werden.

Was verstehst du unter "normal"? Du meinst wohl, dass man das generische implizit und das nicht generische explizit implementieren soll, oder?!

Was soll man dann in der "nicht benötigten" Methode (also in meinem Fall der nicht generischen) machen? Einfach leer lassen, oder das Selbe wie im generischen machen?!

Grüße
Faux
Motzi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2931

XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
BeitragVerfasst: Sa 07.01.06 02:01 
user profile iconfaux hat folgendes geschrieben:
Was verstehst du unter "normal"? Du meinst wohl, dass man das generische implizit und das nicht generische explizit implementieren soll, oder?!

Ja, meinte explizit.. ist mir vorher auf die schnelle nicht eingefalln.. ;)

user profile iconfaux hat folgendes geschrieben:
Was soll man dann in der "nicht benötigten" Methode (also in meinem Fall der nicht generischen) machen? Einfach leer lassen, oder das Selbe wie im generischen machen?!

Das IEnumerator-Interface schaut so aus:
ausblenden C#-Quelltext
1:
public interface IEnumerator<T> : IDisposable, IEnumerator					

Stammt also von IEnumerator ab. Du kannst also von der Implementierung der nicht generischen GetEnumerator-Methode einfach die generische aufrufen. Dann bekommst du einen generischen Enumerator, der ja aber vom nicht generischen Abstammt, also auch als solcher eingesetzt werden kann...

Gruß, Motzi

_________________
gringo pussy cats - eef i see you i will pull your tail out by eets roots!
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 07.01.06 02:13 
user profile iconfaux hat folgendes geschrieben:
NACHTRAG:
@Christian:
Es geht deswegen, weil du das Interface nicht implementierst. ;)
Das war genau mein Punkt. Ich brauche es nicht, es geht auch so. Ich war davon ausgegangen, dass Du Deine Klasse nur in foreach benutzen willst und Du nicht unbedingt das Interface implementieren willst, wenn es auch anders geht.

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

Win XP Pro
C# (VS 2005), Delphi 7
BeitragVerfasst: Sa 07.01.06 11:05 
user profile iconChristian S. hat folgendes geschrieben:
user profile iconfaux hat folgendes geschrieben:
NACHTRAG:
@Christian:
Es geht deswegen, weil du das Interface nicht implementierst. ;)
Das war genau mein Punkt. Ich brauche es nicht, es geht auch so. Ich war davon ausgegangen, dass Du Deine Klasse nur in foreach benutzen willst und Du nicht unbedingt das Interface implementieren willst, wenn es auch anders geht.


Wenn man es so sieht, braucht man ja garkeine Interfaces, weil man ja sowieso jede Methode die ein Interface hätte in die Klasse schreiben kann und das Interface selbst aber nicht implementieren. Aber der Sinn von Interfaces ist es imho ja, dass man sofort Sieht: "Ah, diese Klasse implementiert IEnumerable, also kann ich sie per foreach durchlaufen.".

Eine Frage hätte ich noch:

Wo liegt jetzt der kongrete Unterschied zwischen IEnumerable und IEnumerable<string> in meiner Klasse. Also wieso bindet der Autor meines Buches IEnumerable<string> ein, und nicht einfach nur IEnumerable?

Grüße
Faux
Motzi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2931

XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
BeitragVerfasst: Sa 07.01.06 13:19 
Generische Interfaces/Klassen bieten eine bessere Typsicherheit als nicht generische. Nehmen wir mal die beiden Versionen des IList-Interface.
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
  /* gekürzt */
  public interface IList: ICollection, IEnumerable
  {
    object this[int index] { get; set; }
    int IndexOf(object value);
    void Insert(int index, object value);
    void RemoveAt(int index);
  }

Das nicht generische Interface ist sehr allgemein gehalten - man kann alle Objekte die von object abstammen hinzufügen und man kann daher auch nur den kleinsten gemeinsamen Nenner (object) auslesen. Man muss also erst explizit auf einen anderen Typ casten wenn man einen solchen hinzugefügt hat. Nun kann man zu so einer Liste aber jede Art von Objekt hinzufügen, auch durcheinander. Sollte so eine Liste nun verschiedene Objekte der Klassen Object und String enthalten kann es durchaus passieren, dass ich wenn ich ein String-Objekt auslesen will, und daher entsprechend casten muss, auf ein Object-Objekt treffe und der Typcast daher fehlschlägt -> Exception.

Das generische Interface IList<T> hingegen nimmt einen Typparameter - hier "T" genannt. Dieser Typparameter taucht in der ganzen Typdeklaration gleich mehrmals auf:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
  public interface IList<T>: ICollection<T>, IEnumerable<T>, IEnumerable
  {
    T this[int index] { get; set; }
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);
  }

Typparameter können fast genauso wie normale Klassen eingesetzt werden. Wenn man dieses Interface nun einsetzt muss man für den Typparameter eine konkrete Klasse einsetzen - zB: IList<string>. Vereinfach kann man sich das so vorstellen, dass der Compiler dann überall wo der Typparameter "T" vorkommt dieser Typparameter durch die Klasse string ersetzt wird. Im Endeffekt schaut das Interface dann so aus:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
  public interface IList<string>: ICollection<string>, IEnumerable<string>, IEnumerable
  {
    string this[int index] { get; set; }
    int IndexOf(string item);
    void Insert(int index, string item);
    void RemoveAt(int index);
  }

Wie man sieht kann man nur mehr Objekte der Klasse string hinzufügen und daher auch solche auslesen -> das lästige (und unsichere) Typcasting entfällt, eine bessere Typsicherheit ist gewährleistet..!

Das war aber erst nur ein kurzer Ausflug in die Generizität! Generizität kann noch viel mehr leisten (gebundene Generizität etc). 8)

Gruß, Motzi

_________________
gringo pussy cats - eef i see you i will pull your tail out by eets roots!
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 07.01.06 13:35 
user profile iconfaux hat folgendes geschrieben:
Wenn man es so sieht, braucht man ja garkeine Interfaces, weil man ja sowieso jede Methode die ein Interface hätte in die Klasse schreiben kann und das Interface selbst aber nicht implementieren.
IIRC geht diese "Abkürzung" nur bei Iteratoren.

user profile iconfaux hat folgendes geschrieben:
Aber der Sinn von Interfaces ist es imho ja, dass man sofort Sieht: "Ah, diese Klasse implementiert IEnumerable, also kann ich sie per foreach durchlaufen.".
Okay, das ist natürliche in Argument.

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

Win XP Pro
C# (VS 2005), Delphi 7
BeitragVerfasst: Sa 07.01.06 16:38 
OK, danke euch beiden. Jetzt verstehe ich das. ;)

Kurz gesagt ist es also ein Fehler in meinem Buch, dass das Interface IEnumerable nicht implementiert wird, obwohl es implementiert werden müsste (da eben IEnumerable<T> auch IEnumerable implementiert).

Grüße
Faux