Laden...

[Einführung] Extension Methods in C# 3.0

Erstellt von Khalid vor 16 Jahren Letzter Beitrag vor 16 Jahren 47.934 Views
Khalid Themenstarter:in
3.511 Beiträge seit 2005
vor 16 Jahren
[Einführung] Extension Methods in C# 3.0

Mit C#3.0 finden viele Neuerungen Einzug in das .Net Framework. Neben LINQ, welches wirklich genial ist, gibt es auch ein neues nettes Feature, welches erlaubt bestehende Typen mit eigenen Methoden zu erweitern: Die Extension Methods.

Nehmen wir mal an, in einem Projekt wird immer wieder auf gültige E-Mail Adressen geprüft. Die bis jetzt wohl meist angewendete Methode wird wohl sein, eine statische Klasse zu erstellen, in dem einige Helper-Methoden implementiert sind. So eine Klasse könnte folgendermaßen aussehen


namespace Orcas.Extensions
{
  public static class HelperClass
  {
    public static bool IsValidEMailAddress(string email)
    {
      return true; // Logik einbinden
    }
  }
}

Die Methode in der Klasse IsValidEMailAddress könnte nun wie folgt benutzt werden.


private void button1_Click(object sender, EventArgs e)
{
  string email = "BliBlaBlubb";
  if (Orcas.Extensions.HelperClass.IsValidEMailAddress(email))
    Console.Write("Ist OK!");
}

Viel interessanter wäre es doch, wenn der Type string diese Funktion bereits bereitstellen würde. Dann könnte man im Code einfach folgendes schreiben


private void button1_Click(object sender, EventArgs e)
{
  string email = "BliBlaBlubb";
  if (email.IsValidEMailAddress())
    Console.WriteLine("Ist OK!");
}

Mit den neuen Extension Methods in C#3.0 ist das jetzt endlich möglich. Die Änderung, die gemacht werden muss, um das zu erreichen sind minimal. Die HelperClass muss nur an einer Stelle angepasst werden, damit das oben gezeigte Beispiel funktioniert.


namespace Orcas.Extensions
{
  public static class HelperClass
  {
    public static bool IsValidEMailAddress(this string email)
    {
      return true; // Logik einbinden
    }
  }
}

Der Unterschied zur oberen Deklaration unterscheidet sich nur in den Schlüsselwort this in der Methodendeklaration. Wird in der Deklaration this verwendet vor dem ersten Parameter, wird dem Compiler mitgeteilt, das es sich hierbei um eine Extension für den Type string handelt.
Der Namespace Orcas.Extensions muss eingebunden werden, damit IntelliSense die Extension erkennt. Diese bietet er dann auch sofort in seiner Auswahlliste mit an. Die Extension Methode kann natürlich dann auch mit allen anderen Methoden des Typs verbunden werden.
In den bisher gezeigten Beispiel könnte es dann zum Beispiel auch so aussehen


private void button1_Click(object sender, EventArgs e)
{
  string email = "BliBlaBlubb";
  if (email.Trim().ToLower().IsValidEMailAddress())
    Console.WriteLine("Ist OK!");
}

Wie man sieht, spart man sich durch Extensions einige Zeilen Code. Leider haben Extensions nicht nur Vorteile, sondern auch einige Nachteile.
Extensions haben die niedrigste Priorität beim Compilieren. Nehmen wir an, das eine selbtgeschriebene Klasse die Methode IsValid implementiert hat, die public ist. Wird jetzt eine Extension für die Klasse geschrieben, die den Namen IsValid hat, wird diese von IntelliSense nicht angeboten und auch der Compiler wird diese ignorieren. Das heißt, das man immer die definiton des Basistyps im Auge halten sollte.

Zudem ist es umständlich Extensions zu debuggen. In der aktuellen Beta 1 von Orcas hat bei mir der Compiler immer mal wieder versagt in den Aufruf der Extension zu springen. Ich denke aber mal, das das mit den neueren Versionen behoben sein wird. Aussagen von MS dazu, habe ich nicht gefunden.

Bis dahin
Khalid

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

S
1.047 Beiträge seit 2005
vor 16 Jahren

Glaub da werden sich so einige freuen. 🙂

S
8.746 Beiträge seit 2005
vor 16 Jahren

Aus Qualitätssicht kommt das gleich nach Goto. Von Kapselung keine Spur. Kommt nicht in meinen Code....

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo svenson,

soweit ich das beurteilen kann, wird die Kapselung durch Extension Methods überhaupt nicht verletzt. Eine Extension Method kann nur auf die öffentlichen Member der zu erweiternden Klasse zugreifen, oder? Es geht also nur um etwas syntaktischen Zucker beim Aufruf der Methoden: email.IsValidEMailAddress() statt HelperClass.IsValidEMailAddress(email).

herbivore

Khalid Themenstarter:in
3.511 Beiträge seit 2005
vor 16 Jahren

Richtig, eine Extension Method kann nur auf die öffentlichen Member/Methoden eines Typs zugreifen. Die Kapslung wird in keiner weise verletzt. Es ist, wie herbivore schon sagt, einfach nur ein "syntaktischer Zucker".

BTW: LINQ basiert vollkommen auf Extension Methods. Ich denke mal, wenn das die Kapselung verletzen würde, würde MS das nicht so benutzen.

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

5.941 Beiträge seit 2005
vor 16 Jahren

Hallo Khalid

Danke für diese Darstellung.
Das sieht doch richtig cool aus 🙂

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

S
8.746 Beiträge seit 2005
vor 16 Jahren

Original von herbivore
soweit ich das beurteilen kann, wird die Kapselung durch Extension Methods überhaupt nicht verletzt.

Das kommt darauf an, was man unter Kapselung versteht. Tendenziell bieten Extension Methods die Möglichkeit die Applikationslogik wild in verschiedenen Fremdklassen zu verstecken. Erhöht nicht gerade die Wartbarkeit.

Dann wäre da noch die unsichtbare Kopplung. Klasse A erweitert Klasse B. Die Erweiterung von B wird genutzt von C. Obwohl A nicht von C genutzt wird, entsteht eine Koppelung über B. Wir A aus dem Code entfernt wird, läuft C nicht mehr.

Was passiert eigentlich, wenn zwei Assemblies gleichlautende Erweiterungen vornehmen aber dann dynamisch zusammen in eine AppDomain geladen werden? Gibt es dann zwei String-Klassen? Gewinnt einer?

5.941 Beiträge seit 2005
vor 16 Jahren

Hallo svenson

Ich denke darüber könnte man tagelang streiten.
Es ist doch aber so, wenn man konsequent vorgeht, hat man eine einzige Helper Klasse, die dann überall genutzt wird.
Evt. sogar Global für alle Projekte, was IMO wirklich Sinn machen würde.

So gesehen, ergeben sich "keine" Probleme.
Höchstens, wenn diese Helperklassen dann in einer Komponente mitgeliefert werden, oä.. Da könnte es tatsächlich krachen.

Aber dort sollte man halt Abstand von diesen Methoden nehmen~
Oder aber Präfixe für die Methoden verwenden... dann kann man dieser "syntactic sugar" aber gleich weglassen (Bei den Komponenten) 😉

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo svenson,

Das kommt darauf an, was man unter Kapselung versteht. Tendenziell bieten Extension Methods die Möglichkeit die Applikationslogik wild in verschiedenen Fremdklassen zu verstecken. Erhöht nicht gerade die Wartbarkeit.

das stimmt einerseits und andererseits nicht. Denn wenn die zu erweiternde Klasse die benötigte Funktionalität nicht bietet, muss diese so oder so in eine zusätzliche (Helper-)Klasse. Durch Extension Methods wird jetzt aber wenigstens der sonst nur implizite Zusammenhang zwischen Helperklasse und eigentlicher Klasse explizit ausgedrückt. Es wird also was die Kapselung angeht also eher besser denn schlechter.

Dann wäre da noch die unsichtbare Kopplung. Klasse A erweitert Klasse B. Die Erweiterung von B wird genutzt von C. Obwohl A nicht von C genutzt wird, entsteht eine Koppelung über B. Wir A aus dem Code entfernt wird, läuft C nicht mehr.

Das stimmt nicht. Denn da der erste Parameter der Extension Method zwangsläufig vom Typ A ist, besteht die Koppelung zwischen A und C nicht nur implizit, sondern auch explizit.

Was passiert eigentlich, wenn zwei Assemblies gleichlautende Erweiterungen vornehmen aber dann dynamisch zusammen in eine AppDomain geladen werden? Gibt es dann zwei String-Klassen? Gewinnt einer?

Da AppDomains weitgehend gegeneinander entkoppelt sind, spielt es in der einen AppDomain keine Rolle, ob es in der anderen AppDomain genauso oder anders ist.

Du hast natürlich recht, dass man mit Extension Methods auch Schindluder treiben kann, aber mir scheint, dass deine Vorbehalte übertrieben sind.

herbivore

S
8.746 Beiträge seit 2005
vor 16 Jahren

Original von herbivore
das stimmt einerseits und andererseits nicht.

Wenn man sauber arbeitet und alle Extensions in wirklichen Helper-Klassen unterbringt, dann ist es im Prinzip kein Unterschied. Aber da die Notwendigkeit von Helperklassen entfällt wird Faulheit obsiegen... 🙂

Das stimmt nicht. Denn da der erste Parameter der Extension Method zwangsläufig vom Typ A ist, besteht die Koppelung zwischen A und C nicht nur implizit, sondern auch explizit.

Der erste Parameter ist vom Typ B, oder? IsValidEmail() bekommt ja auch nur (this) string als Parameter.

Da AppDomains weitgehend gegeneinander entkoppelt sind, spielt es in der einen AppDomain keine Rolle, ob es in der anderen AppDomain genauso oder anders ist.

Es geht doch gerade darum, dass zwei statisch compilierte Module (Assemblies) in ein dynamisches Umfeld geladen werden. Da bisher auch alle Typen statisch waren, gab es bestenfalls Versionskonflikte. Jetzt scheint mir das Szenario weitaus komplexer. Im Prinzip müßte jede Klassen eine Kopie derjenigen Klassen erhalten, die sie erweitert. Aber wie schaut es dann noch mit Typidentität aus? Welche Auswirkungen hat das auf Reflection?

Eigentlich kann es wirklich nur so sein wie du sagtest: In Wirklichkeit wird nur eine Helperklasse erzeugt und der Compiler besorgt den Rest.

Damit wäre auch das obige Problem aus der Welt. Verschärft aber leider das Wartbarkeitsproblem. Man könnte wirklich zwei Methoden gleichen Namens haben, die eine Klasse erweitern. Je nach Assembly-Zugehörigkeit wird die jeweilige Implementierung aufgerufen. Da wäre es doch schön, wenn Extensions auf internal-Sichtbarkeit beschränkt blieben.

Damit wären Extension Methods wirklich rein syntaktischer Zucker und somit Reflection nicht zugänglich (zumindest nicht in der erweiterten Klasse). D.h. es werden keine Klassen erweitern, sondern man kann Code so schreiben als WÄREN sie erweitert. Und das ganze funktioniert nur mit statischer Bindung.

*EDIT*

Ist tatsächlich so. Es gibt keine Erweiterung von Klassen zur Laufzeit in .NET. Extensions Methods sind reine Abkürzungsschreibweisen von Aufrufen über statische, automatisch generierte Helperklassen. Ob das aus Sicht der Codewartbarkeit ein Gewinn darstellt wird sich zeigen. Zumindest wird es der Lesbarkeit zuträglich sein....

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo svenson,

Der erste Parameter ist vom Typ B, oder?

du hast Recht. Ich hatte das falsch herum gelesen. Also das B die Klasse ist, die A erweitert.

Da A aber die Klasse mit der Extension Method ist, hast du natürlich recht. Der Typ A taucht in C nicht auf. Allerdings besteht der Bezug immer noch über die Methode (weshalb der Code ja auch nicht mehr läuft bzw. sich erst gar nicht übersetzen lassen wird, wenn man A (und damit die Extension Methode) entfernt. Deshalb sehe ich trotzdem kein Problem.

herbivore

6.862 Beiträge seit 2003
vor 16 Jahren

Original von svenson

Das stimmt nicht. Denn da der erste Parameter der Extension Method zwangsläufig vom Typ A ist, besteht die Koppelung zwischen A und C nicht nur implizit, sondern auch explizit.

Der erste Parameter ist vom Typ B, oder? IsValidEmail() bekommt ja auch nur (this) string als Parameter.

Ja, er ist vom Typ B um bei deinem Beispiel zu bleiben. Trotzdem ist die Kopplung explizit, da du explizit den Typen einbinden musst(meist einfach per using den entsprechenden Namespace) in dem die Extension Method definiert wird, weil...

Obwohl A nicht von C genutzt wird, entsteht eine Koppelung über B.

Nein, durch die Benutzung der Extension Method wird A von C genutzt! Nehmen wir mal an wir hätten eine Extension Method für String die sich WhitespaceToUnderscore nennt:


namespace ExtensionMethods {
    public static class Extensions {
        public static string WhiteSpaceToUnderscore(this string param) {
            return param.Replace(' ', '_');
        }
    }
}

Und wir haben irgendwo nen Hauptprogramm


using ExtensionMethods;

namespace ConsoleApplication1 {
    class Program {
        static void Main(string[] args) {
            Console.WriteLine("Hallo Welt!".WhiteSpaceToUnderscore());
            Console.ReadLine();
        }
    }
}

Nie gefragt wieso die Klasse und die Extension Method statisch sein müssen?
Der Compiler macht einfach das draus:

Console.WriteLine(Extensions.WhiteSpaceToUnderscore("Hallo Welt!"));

mehr steckt da nicht hinter, keine generierten Helperklassen etc. Nur nen bissle syntaktischer Zucker.

In dieser, ich sag mal expliziten Schreibweise, erübrigt sich auch das Problem mit der Mehrdeutigkeit wenn zwei gleichlautende Extension Methods dynamisch geladen werden aus verschiedenen Typen, hier in dem Fall ist der nämlich mit angegeben. Aber dynamisch fällt mir grad auch gar net ein wie ich ne Extension Method invoken könnte auf einen bestimmten Typ, da fällt mir auch nur diese explizite Variante ein. Glaube da sind Extension Methods echt harmloser als es den anschein hat.

Baka wa shinanakya naoranai.

Mein XING Profil.

4.207 Beiträge seit 2003
vor 16 Jahren

Auf der PDC 2005, wo C# 3.0 vorgestellt wurde, hieß es von MS sinngemäß:

Extension Methods mussten in C# 3.0 wegen Linq rein, der Anwender soll nach Möglichkeit aber die Finger davon lassen, weil das Gefahrenpotenzial, Mist damit zu machen, viel zu groß ist.

Die Aussage kam IIRC von Anders Heijlsberg.

Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de

S
8.746 Beiträge seit 2005
vor 16 Jahren

Original von talla
Nie gefragt wieso die Klasse und die Extension Method statisch sein müssen?

Ach, dass auch die Klasse statisch sein muss, war mir entgangen. Das entkräftet zumindest die meisten der Kritikpunkte. Die unsichtbaren Kopplungen bleiben allerdings. Aber da Extensions ja im IL-Code ausgezeichnet werden, kann ein Visualisierungstool diese Beziehungen ja wenigstens sichtbar machen.

Ist in Extension Methods eigentlich der Zugriff auf private Member der zu erweiternden Klasse erlaubt? Dürfte ja eigentlich nicht der Fall sein.

Khalid Themenstarter:in
3.511 Beiträge seit 2005
vor 16 Jahren

Sorry, hätte ich im Artikel vielleicht erwähnen sollen, das die Extensions nur in statischen Klassen erlaubt sind.

Zugriff auf private Member der zu erweiternden Klasse ist nicht möglich, weil dann könnte man damit wirklich bockmist machen.

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

347 Beiträge seit 2006
vor 16 Jahren

Original von svenson
Ach, dass auch die Klasse statisch sein muss, war mir entgangen. Das entkräftet zumindest die meisten der Kritikpunkte. Die unsichtbaren Kopplungen bleiben allerdings. Aber da Extensions ja im IL-Code ausgezeichnet werden, kann ein Visualisierungstool diese Beziehungen ja wenigstens sichtbar machen. Ex-Methods sind nix weiter als statische Methoden, die mit dem ExtensionAttribute markiert werden, die Klasse selbst muss ebenfalls statisch sein und auch mit dem gleichen Attribut markiert sein.

Das ganze existiert auch nur in deinem Source, nicht im IL code.
Dem Compiler wird durch die Attribute nur gezeigt, dass er diese komische Methode, die er gar nicht in der Klasse finden kann, in einer andere Klasse suchen könnte.
Der Namespace der Extension class muss übrigens ebenfalls als using-clause benutzt werden.

Ist in Extension Methods eigentlich der Zugriff auf private Member der zu erweiternden Klasse erlaubt? Dürfte ja eigentlich nicht der Fall sein. Wie gesagt: keine Zauberei, nur statische Methoden.

Und wirklich Sinn macht das eigentlich nur bei generischen Methoden.
Nehmen wir mal einen abtrakten Bleistift:

[Extension]
public static class Miep
{
  [Extension]
  public static IEnumerable<T> GetItemsGreaterThan<T>(IEnumerable<T> items, IComparable<T> minValue)
  {
     foreach(T item in items)
       if(minValue.CompareTo(item) < 0)
         yield return item;
  }
}

Das ganze ließe sich jetzt so aufrufen:

int[] someInts = {1, 2, 3, 4, 5, 6};
foreach(int found in Miep.GetItemsGreaterThan<int>(someInts , 3))
  Console.WriteLine(found);

IMHO hübscher wäre es so:

int[] someInts = {1, 2, 3, 4, 5, 6};
foreach(int found in someInts.GetItemsGreaterThan(3))
  Console.WriteLine(found);

Wenn man es sinnvoll verwendet, lassen sich auf die Art etwas zu wortreiche Zeilen schön vereinfachen. (Für den Leser)

LINQ selbst benutzt Ex-Methods nur indirekt, da wir es dort mit syntaktischen Tricks zu tun haben, die wieder über syntaktische Tricks gestülpt werden.
Wenn deine Klasse/Interface zum Beispiel kein OrderBy enthält, aber irgendeine Extension class eine passende Methode zur Verfügung stellt, dann wird dieses OrderBy benutzt.
Praktisch eine Art Duck-typing für statische Methoden. 🤔

btw: Was wirklich interessant an Ex-Methods ist, ist die Frage ob MS sie weit genug abgespeckt hat, um die Patente von CodeGEAR/Borland zu Class helpers nicht zu verletzen. 😁

0
767 Beiträge seit 2005
vor 16 Jahren

Was an ExtensionMethods aucuh wirklich interessant ist, ist folgendes:


interface IBla
{
  string DoSomething(int value, bool yesAll);
}

public static IBlaExtension
{
  public static void DoSomething(this IBla blubb, int value)
  {
    return blubb.DoSomething(value, true);
  }
}

schon hat man ein kleines interface, das aber dennoch einen zweiten overload unterstützt, und das beste: den overload muss nicht jede klasse implementieren, die vom interface ableitet.

(übrigens funktioniert das bei mir auch ohne das ExtensionAttribute).

loop:
btst #6,$bfe001
bne.s loop
rts

I
1.739 Beiträge seit 2005
vor 16 Jahren

@Khalid: Lies mal erst die MS-Doku durch.
Extensions sind Ausnahmen, die man begründen muss. Sie sind Spezialfällen vorbehalten.
Das Verbreiten dieser Mechanismen ist eher kontraproduktiv zu sehen, da exzessive Anwendung einfach nur der kürzeste Weg zur Müllhalde ist.
Ähnliche Mechanismen gab es schon seit 1.0(vor allem im Bereich Controls), das vwurde jetzt beträchtlich Erweitert. Ich sehe das eher als Gefahr für wiederverwendbaren Code, wenn es ohne zu Hinterfragen für jeden Kram missbraucht wird.
Eigentlich steckt ja nur ein simpler Dekorateur dahinter, man muss ihn aber als solchen Begreifen und Anwenden.
Was ich deiner Ausführung bemängele:
-Unreflektive Featuritis
-das Hinstellen als jederzeit gebrauchsfähige Lösung
-kein Hinweis auf Gefahren, Sinn oder Unsinn

Naja, ich kann jetzt nur noch auf die recht schwache technische Hürde hoffen, das jetzt keine Sintflut kommt...