none
Aufrufen generischer Ereiterungsmethoden RRS feed

  • Frage

  • Hallo,

    ich nutze eine Liste, die mit Elementen vom Typ double gefüllt ist. Welchen Unterschied macht es, wenn ich eine Erweiterungsmethode folgendermaßen aufrufe:

    double temp = meineListe.First();

    oder

    double temp = meineList.First<double>();

    Ich bin der Meinung, dass ich keinen Unterschied in der Funktion bekomme und wüsste gerne die Best Practice, oder eben doch die Unterschiede.

     

    Gruß

    Michael

    • Bearbeitet Mike_Th Mittwoch, 8. September 2010 06:42 Rechtschreibung
    Mittwoch, 8. September 2010 06:42

Antworten

  • Hallo Michael,

    es macht keinen Unterschied.
    Der Compiler leitet den Typen anhand des Parameters, hier der Liste ab,
    der für TSource in First ermittelt wird.
    Komplizierter wird es erst, wenn mehrere Typparameter abgeleitet werden müssen.

    Allgemein beschreibt das: Generische Methoden (C#-Programmierhandbuch)
    Willst Du es exakt wissen, so findest Du es in der C# Sprachspezifikation   unter 7.5.2 Type Inference ff.
    Wobei man leichter verständliche ergänzende Kost in Eric Lipperts Blog findet.

    Zur "Best Practice": Im allgemeinen läßt man es weg -
    denn i. a. gilt: Mag der Compiler es ohne nicht, hilft es auch mit nicht.
    Entwirft man eigene Methoden, sollten man darauf achten,
    dass Mehrdeutigkeiten vermieden werden.

    Gruß Elmar

    • Als Antwort markiert Mike_Th Mittwoch, 8. September 2010 17:25
    Mittwoch, 8. September 2010 07:54
    Beantworter
  • Hallo Michael,

    wenn "meineListe" typsicher definiert wurde (etwa: new List<double>...), wird das gleiche IL erzeugt.
    Der Compiler setzt beide Aufrufe intern in die getypte Variante um.
    Insofern ist es nur noch eine eher subjektive Sicht, ob einem <double> da besser gefällt.
    Die meisten benutzten hier IMHO dann die Variante ohne <double> aus meiner "Erfahrung" - ich auch.
    Grund u.a.: weniger zu schreiben, bei trotzdem klarer Typ-Erkennung. ggf. auch weniger Wartungsaufwand bei Änderung, also letztlich dem DRY Prinzip (Don't repeat yourself) folgend.


    ciao Frank
    • Als Antwort markiert Mike_Th Mittwoch, 8. September 2010 07:44
    Mittwoch, 8. September 2010 07:32
  • Hallo Michael,

    wenn Du Dir die Signatur von First genau anschaust, wird IEnumerable<TSource > als Parameter erwartet,
    was wiederum u. a. von IList<T> implementiert wird, somit für List<T> uam. gilt.
    Durch die Angabe von this beim ersten Parameter wird es (in C#) als Erweiterungsmethode gekennzeichnet
    und kann so auf jede Instanz angewendet werden, die IEnumerable<T > implementiert.

    Der C# Compiler probiert jeweils alle implementierten Schnittstellen die Vererbungshierachie durch,
    und wählt die treffendste aus - was extrem vereinfacht ausgedrückt ist; in der C# Spezifikation
    ist das ein höchst komplexes Regelwerk.

    Gruß Elmar
    • Als Antwort markiert Mike_Th Donnerstag, 9. September 2010 05:44
    Mittwoch, 8. September 2010 18:06
    Beantworter
  • Hallo Michael,

        > Wie funktioniert dass dann bei meinem meineListe.First()?

    Du kannst First ja auch überschreiben. Hier ein Beispiel wie First implementiert sein könnte:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Windows.Forms;
    
    namespace WinFirst
    {
     public partial class Form1 : Form
     {
      public Form1()
     {
      InitializeComponent();
     }
     
     private void Form1_Load(object sender, EventArgs e)
     {
      List<double> liste = new List<double> { 1.0, 2.0 };
      var f = liste.First();
     }
     }
    }
    
    public static class MeineErweiterungen
    {
     ///<summary>[Beispiel] Gibt das erste Element einer Sequenz zurück.
     /// Typparameter:
     /// TSource: Der Typ der Elemente von source.
     ///</summary>
     ///<param name="source">
     /// Das System.Collections.Generic.IEnumerable&lt;T&gt;, dessen 
     /// erstes Element zurückgegeben werden soll.
     ///</param> 
     ///<returns>
     /// Rückgabewerte:
     /// Das erste Element in der angegebenen Sequenz.
     ///</returns>
     public static TSource First<TSource>(this IEnumerable<TSource> source)
     {
     CheckNotNull(source, "source");
      var list = source as IList<TSource>; // spezielle Beispiel-Optimierung 
      if (list != null)
      return list.Count > 0 ? list[0] : default(TSource);
    
      using (var e = source.GetEnumerator()) 
      return e.MoveNext() ? e.Current : default(TSource);
     }
    
     [DebuggerStepThrough]
     private static void CheckNotNull<T>(T value, string name) where T : class
     {
      if (value == null) throw new ArgumentNullException(name);
     }
    }
    

    (wobei hier die Überladungpublic static TSource First<TSource>(
          this
     IEnumerable<TSource> source, Func<TSource, bool> predicate);
    mal weggelassen ist. Insofern siehst Du dort, was Parameter in dieser Erweiterungsmethode sind. Anhand dieser entscheidet der Compiler die Wahl der Methode. Gerade mit den in C# 4.0 neu hinzugekommenen optionalen und benannten Parametern, und ebenfalss der erweiterten [Kovarianz und Kontravarianz (beachte, dass IEnumerable<T> jetzt kovariant ist, also kommt Variabilität hinzu] ist die Wahl der Signatur für den Compiler nicht immer klar und sogar in einigen Fällen unentscheidbar. Aber in diesem Fall (First) ist das für den Compiler ja einfach - wie ich ja bereits in meinem ersten Posting schrieb. Ich hoffe, meine Postings waren auch hilfreich ;-)


    ciao Frank
    • Als Antwort markiert Mike_Th Donnerstag, 9. September 2010 05:44
    Mittwoch, 8. September 2010 18:22

Alle Antworten

  • Hallo Michael,

    wenn "meineListe" typsicher definiert wurde (etwa: new List<double>...), wird das gleiche IL erzeugt.
    Der Compiler setzt beide Aufrufe intern in die getypte Variante um.
    Insofern ist es nur noch eine eher subjektive Sicht, ob einem <double> da besser gefällt.
    Die meisten benutzten hier IMHO dann die Variante ohne <double> aus meiner "Erfahrung" - ich auch.
    Grund u.a.: weniger zu schreiben, bei trotzdem klarer Typ-Erkennung. ggf. auch weniger Wartungsaufwand bei Änderung, also letztlich dem DRY Prinzip (Don't repeat yourself) folgend.


    ciao Frank
    • Als Antwort markiert Mike_Th Mittwoch, 8. September 2010 07:44
    Mittwoch, 8. September 2010 07:32
  • Hallo Frank,

    danke für die schnelle Antwort. Die Liste ist typsicher definiert, deswegen habe ich wohl auch keinen Unterschied feststellen können. Werde es dann auch weglassen, jetzt wo ich den Hintergrund kenne.

    Gruß

    Michael

    Mittwoch, 8. September 2010 07:44
  • Hallo Michael,

    es macht keinen Unterschied.
    Der Compiler leitet den Typen anhand des Parameters, hier der Liste ab,
    der für TSource in First ermittelt wird.
    Komplizierter wird es erst, wenn mehrere Typparameter abgeleitet werden müssen.

    Allgemein beschreibt das: Generische Methoden (C#-Programmierhandbuch)
    Willst Du es exakt wissen, so findest Du es in der C# Sprachspezifikation   unter 7.5.2 Type Inference ff.
    Wobei man leichter verständliche ergänzende Kost in Eric Lipperts Blog findet.

    Zur "Best Practice": Im allgemeinen läßt man es weg -
    denn i. a. gilt: Mag der Compiler es ohne nicht, hilft es auch mit nicht.
    Entwirft man eigene Methoden, sollten man darauf achten,
    dass Mehrdeutigkeiten vermieden werden.

    Gruß Elmar

    • Als Antwort markiert Mike_Th Mittwoch, 8. September 2010 17:25
    Mittwoch, 8. September 2010 07:54
    Beantworter
  • Allgemein beschreibt das: Generische Methoden (C#-Programmierhandbuch)

    Hallo Elmar,

    vielen Dank für die ausführlichen Infos. Die Ausführungen im ersten Link habe ich jetzt mal gelesen und mir ist folgendes aufgefallen:

    Der Compiler kann Typparameter auf der Grundlage der übergebenen Methodenargumente ableiten. Eine Einschränkung oder ein Rückgabewert genügen ihm zur Ableitung des Typparameters nicht. Infolgedessen ist ein Typrückschluss bei Methoden ohne Parameter nicht möglich.

    Wie funktioniert dass dann bei meinem meineListe.First()? Da habe ich auch keine Parameter. Du schriebst, der Parameter sei die Liste. Wie darf ich das deuten?


    Gruß

    Michael

    • Bearbeitet Mike_Th Mittwoch, 8. September 2010 17:31 Ergänzung
    Mittwoch, 8. September 2010 17:28
  • Hallo Michael,

    wenn Du Dir die Signatur von First genau anschaust, wird IEnumerable<TSource > als Parameter erwartet,
    was wiederum u. a. von IList<T> implementiert wird, somit für List<T> uam. gilt.
    Durch die Angabe von this beim ersten Parameter wird es (in C#) als Erweiterungsmethode gekennzeichnet
    und kann so auf jede Instanz angewendet werden, die IEnumerable<T > implementiert.

    Der C# Compiler probiert jeweils alle implementierten Schnittstellen die Vererbungshierachie durch,
    und wählt die treffendste aus - was extrem vereinfacht ausgedrückt ist; in der C# Spezifikation
    ist das ein höchst komplexes Regelwerk.

    Gruß Elmar
    • Als Antwort markiert Mike_Th Donnerstag, 9. September 2010 05:44
    Mittwoch, 8. September 2010 18:06
    Beantworter
  • Hallo Michael,

        > Wie funktioniert dass dann bei meinem meineListe.First()?

    Du kannst First ja auch überschreiben. Hier ein Beispiel wie First implementiert sein könnte:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Windows.Forms;
    
    namespace WinFirst
    {
     public partial class Form1 : Form
     {
      public Form1()
     {
      InitializeComponent();
     }
     
     private void Form1_Load(object sender, EventArgs e)
     {
      List<double> liste = new List<double> { 1.0, 2.0 };
      var f = liste.First();
     }
     }
    }
    
    public static class MeineErweiterungen
    {
     ///<summary>[Beispiel] Gibt das erste Element einer Sequenz zurück.
     /// Typparameter:
     /// TSource: Der Typ der Elemente von source.
     ///</summary>
     ///<param name="source">
     /// Das System.Collections.Generic.IEnumerable&lt;T&gt;, dessen 
     /// erstes Element zurückgegeben werden soll.
     ///</param> 
     ///<returns>
     /// Rückgabewerte:
     /// Das erste Element in der angegebenen Sequenz.
     ///</returns>
     public static TSource First<TSource>(this IEnumerable<TSource> source)
     {
     CheckNotNull(source, "source");
      var list = source as IList<TSource>; // spezielle Beispiel-Optimierung 
      if (list != null)
      return list.Count > 0 ? list[0] : default(TSource);
    
      using (var e = source.GetEnumerator()) 
      return e.MoveNext() ? e.Current : default(TSource);
     }
    
     [DebuggerStepThrough]
     private static void CheckNotNull<T>(T value, string name) where T : class
     {
      if (value == null) throw new ArgumentNullException(name);
     }
    }
    

    (wobei hier die Überladungpublic static TSource First<TSource>(
          this
     IEnumerable<TSource> source, Func<TSource, bool> predicate);
    mal weggelassen ist. Insofern siehst Du dort, was Parameter in dieser Erweiterungsmethode sind. Anhand dieser entscheidet der Compiler die Wahl der Methode. Gerade mit den in C# 4.0 neu hinzugekommenen optionalen und benannten Parametern, und ebenfalss der erweiterten [Kovarianz und Kontravarianz (beachte, dass IEnumerable<T> jetzt kovariant ist, also kommt Variabilität hinzu] ist die Wahl der Signatur für den Compiler nicht immer klar und sogar in einigen Fällen unentscheidbar. Aber in diesem Fall (First) ist das für den Compiler ja einfach - wie ich ja bereits in meinem ersten Posting schrieb. Ich hoffe, meine Postings waren auch hilfreich ;-)


    ciao Frank
    • Als Antwort markiert Mike_Th Donnerstag, 9. September 2010 05:44
    Mittwoch, 8. September 2010 18:22
  • Hallo Elmar,

    jetzt wird einiges klarer. Die Signatur und wie man Erweiterungsmethoden deklariert hatte ich im MSDN schon gesehen. Nur war ich in der Funktionsweise verunsichert, weil ich IEnumerable<TSource> ja nicht wissentlich in den Klammern () übergebe, wie ich das bei anderen Methoden gewöhnt bin.

    Was deinen zweiten Absatz angeht kann ich nur zustimmen. Das muss ich nicht alles verstehen ;-). Ich habe mich an Eric Lipperts Blog gehalten.

     

    Gruß

    Michael

    Donnerstag, 9. September 2010 05:38
  • Hallo Frank,

    das hilft auch noch mal. Hatte mir Erweiterungsmethoden ja im MSDN schon angeschaut (s. Antwort an Elmar) und ich habe bei Eric gelesen wie umfangreich das ist, denn richtigen Typ abzuleiten.

    >Ich hoffe, meine Postings waren auch hilfreich ;-)

    Darüber brauchst du dir keine Sorgen machen. Auf Grund des Wissengefälles von oben nach unten, werden deine Postings bei meinen Fragen in der Regel hilfreich sein ;-).

    Gruß

    Michael

    Donnerstag, 9. September 2010 05:44
  • Hallo Michael,

    die Regeln sind so komplex, das auch für ausgewiesene Experten wie Jon Skeet
    überrraschendes zu Tage kommt: Dynamic type inference and surprising possibilities

    Und ich werde niemals behaupten - weder heute noch in Zukunft -
    die Kapitel in der C# Spezifikation 100% verinnerlicht zu haben ;-)

    Gruß Elmar

    Donnerstag, 9. September 2010 08:18
    Beantworter