Suche…


Syntax

  • public static ReturnType MyExtensionMethod (dieses TargetType-Ziel)
  • public static ReturnType MyExtensionMethod (dieses TargetType-Ziel, TArg1 arg1, ...)

Parameter

Parameter Einzelheiten
diese Dem ersten Parameter einer Erweiterungsmethode sollte immer das Schlüsselwort this vorangestellt werden, gefolgt von der Kennung, mit der auf die "aktuelle" Instanz des zu erweiternden Objekts verwiesen wird

Bemerkungen

Erweiterungsmethoden sind syntaktischer Zucker, mit dem statische Methoden für Objektinstanzen aufgerufen werden können, als wären sie ein Mitglied des Typs.

Erweiterungsmethoden erfordern ein explizites Zielobjekt. Sie müssen das Schlüsselwort this um auf die Methode innerhalb des erweiterten Typs selbst zuzugreifen.

Erweiterungsmethoden müssen als statisch deklariert werden und in einer statischen Klasse leben.

Welcher Namensraum?

Die Wahl des Namespaces für Ihre Erweiterungsmethodenklasse ist ein Kompromiss zwischen Sichtbarkeit und Erkennbarkeit.

Die am häufigsten genannte Option ist ein benutzerdefinierter Namespace für Ihre Erweiterungsmethoden. Dies erfordert jedoch einen Kommunikationsaufwand, sodass die Benutzer Ihres Codes wissen, dass die Erweiterungsmethoden vorhanden sind, und wo sie zu finden sind.

Alternativ können Sie einen Namespace auswählen, sodass Entwickler Ihre Erweiterungsmethoden über Intellisense ermitteln können. Wenn Sie also die Foo Klasse erweitern möchten, ist es logisch, die Erweiterungsmethoden in den gleichen Namespace wie Foo .

Es ist wichtig zu IEnumerable , dass nichts Sie daran hindert, den Namespace "einer anderen Person" zu verwenden : Wenn Sie IEnumerable erweitern IEnumerable , können Sie Ihre Erweiterungsmethode im System.Linq Namespace System.Linq .

Dies ist nicht immer eine gute Idee. In einem bestimmten Fall möchten Sie beispielsweise einen allgemeinen Typ erweitern (z. B. bool IsApproxEqualTo(this double value, double other) ), ohne jedoch das gesamte System "verschmutzen". In diesem Fall sollte ein lokaler, spezifischer Namespace ausgewählt werden.

Schließlich ist es auch möglich, die Erweiterungsmethoden in keinen Namensraum zu setzen !

Eine gute Referenzfrage: Wie verwalten Sie die Namespaces Ihrer Erweiterungsmethoden?

Anwendbarkeit

Beim Erstellen von Erweiterungsmethoden ist darauf zu achten, dass sie für alle möglichen Eingaben geeignet sind und nicht nur für bestimmte Situationen relevant sind. Zum Beispiel ist es möglich, Systemklassen wie string , wodurch der neue Code für jeden String verfügbar ist. Wenn Ihr Code domänenspezifische Logik für ein domänenspezifisches Zeichenfolgenformat ausführen muss, ist eine Erweiterungsmethode nicht geeignet, da das Vorhandensein von Aufrufern die Verwendung anderer Zeichenfolgen im System verwechseln würde.

Die folgende Liste enthält grundlegende Funktionen und Eigenschaften von Erweiterungsmethoden

  1. Es muss eine statische Methode sein.
  2. Es muss sich in einer statischen Klasse befinden.
  3. Es verwendet das Schlüsselwort "this" als ersten Parameter mit einem Typ in .NET. Diese Methode wird von einer bestimmten Typinstanz auf der Clientseite aufgerufen.
  4. Es wird auch von VS Intellisense gezeigt. Wenn wir den Punkt drücken . Nach einer Typinstanz kommt es dann in VS Intellisense.
  5. Eine Erweiterungsmethode sollte sich in demselben Namespace befinden wie sie verwendet wird, oder Sie müssen den Namespace der Klasse mithilfe einer using-Anweisung importieren.
  6. Sie können einen beliebigen Namen für die Klasse angeben, die über eine Erweiterungsmethode verfügt, die Klasse sollte jedoch statisch sein.
  7. Wenn Sie einem Typ neue Methoden hinzufügen möchten und nicht über den Quellcode dafür verfügen, können Sie Erweiterungsmethoden dieses Typs verwenden und implementieren.
  8. Wenn Sie Erweiterungsmethoden erstellen, die dieselbe Signaturmethode wie der Typ haben, den Sie erweitern, werden die Erweiterungsmethoden niemals aufgerufen.

Erweiterungsmethoden - Übersicht

Erweiterungsmethoden wurden in C # 3.0 eingeführt. Erweiterungsmethoden erweitern vorhandene Verhaltenstypen und fügen sie hinzu, ohne dass ein neuer abgeleiteter Typ erstellt, neu kompiliert oder der ursprüngliche Typ anderweitig geändert wird. Sie sind besonders hilfreich, wenn Sie die Quelle eines Typs, den Sie verbessern möchten, nicht ändern können. Erweiterungsmethoden können für Systemtypen, von Dritten definierte Typen und von Ihnen selbst definierte Typen erstellt werden. Die Erweiterungsmethode kann aufgerufen werden, als wäre sie eine Member-Methode des ursprünglichen Typs. Dies ermöglicht, dass Method Chaining zur Implementierung einer Fluent-Schnittstelle verwendet wird .

Eine Erweiterungsmethode wird erstellt, indem einer statischen Klasse eine statische Methode hinzugefügt wird , die sich vom ursprünglichen Typ unterscheidet, der erweitert wird. Die statische Klasse, die die Erweiterungsmethode enthält, wird häufig nur zum Zweck des Haltens von Erweiterungsmethoden erstellt.

Erweiterungsmethoden benötigen einen speziellen ersten Parameter, der den ursprünglichen Typ angibt, der erweitert wird. Dieser erste Parameter wird mit dem Schlüsselwort verziert this (die eine besondere und unterschiedliche Verwendung stellt this in C # -en sollte so verschieden von der Verwendung zu verstehen this , die an den Mitglieder der aktuellen Objektinstanz ermöglicht Bezug).

Im folgenden Beispiel wird der ursprüngliche Typ erweitert die Klasse string . String wurde um die Methode Shorten() , die die zusätzliche Funktion der Verkürzung bietet. Die statische Klasse StringExtensions wurde erstellt, um die Erweiterungsmethode aufzunehmen. Die Erweiterungsmethode Shorten() zeigt an, dass es sich um eine Erweiterung von string über den speziell markierten ersten Parameter handelt. Um zu zeigen, dass die Shorten() -Methode den string , wird der erste Parameter mit this markiert. Daher ist die vollständige Signatur des ersten Parameters this string text , wobei string der ursprüngliche Typ ist, der erweitert wird, und text der gewählte Parametername ist.

static class StringExtensions
{
    public static string Shorten(this string text, int length) 
    {
        return text.Substring(0, length);
    }
}

class Program
{
    static void Main()
    {
        // This calls method String.ToUpper()
        var myString = "Hello World!".ToUpper();

        // This calls the extension method StringExtensions.Shorten()
        var newString = myString.Shorten(5); 

        // It is worth noting that the above call is purely syntactic sugar
        // and the assignment below is functionally equivalent
        var newString2 = StringExtensions.Shorten(myString, 5);
    }
}

Live-Demo zu .NET-Geige


Das Objekt, das als erstes Argument einer Erweiterungsmethode (mit dem Schlüsselwort this begleitet) übergeben wird, ist die Instanz, für die die Erweiterungsmethode aufgerufen wird.

Zum Beispiel, wenn dieser Code ausgeführt wird:

"some string".Shorten(5);

Die Werte der Argumente lauten wie folgt:

text: "some string"
length: 5

Beachten Sie, dass Erweiterungsmethoden nur verwendet werden können, wenn sie sich im selben Namespace befinden wie ihre Definition, wenn der Namespace explizit durch den Code mithilfe der Erweiterungsmethode importiert wird oder wenn die Erweiterungsklasse ohne Namespace ist. In den .NET Framework-Richtlinien wird empfohlen, Erweiterungsklassen in einem eigenen Namespace abzulegen. Dies kann jedoch zu Ermittlungsproblemen führen.

Dies führt zu keinen Konflikten zwischen den Erweiterungsmethoden und den verwendeten Bibliotheken, es sei denn, Namespaces, die in Konflikt stehen könnten, werden explizit eingezogen. Zum Beispiel LINQ-Erweiterungen :

using System.Linq; // Allows use of extension methods from the System.Linq namespace

class Program
{
    static void Main()
    {
        var ints = new int[] {1, 2, 3, 4};

        // Call Where() extension method from the System.Linq namespace
        var even = ints.Where(x => x % 2 == 0); 
    }
}

Live-Demo zu .NET-Geige


Seit C # 6.0 ist es auch möglich, eine using static Direktive in die Klasse zu setzen, die die Erweiterungsmethoden enthält. using static System.Linq.Enumerable; Sie beispielsweise using static System.Linq.Enumerable; . Dadurch werden Erweiterungsmethoden dieser bestimmten Klasse verfügbar, ohne dass andere Typen aus demselben Namespace in den Gültigkeitsbereich aufgenommen werden.


Wenn eine Klassenmethode mit derselben Signatur verfügbar ist, priorisiert der Compiler diese dem Aufruf der Erweiterungsmethode. Zum Beispiel:

class Test
{
   public void Hello()
   {
       Console.WriteLine("From Test");
   }
}

static class TestExtensions
{
    public static void Hello(this Test test)
    {
        Console.WriteLine("From extension method");
    }
}

class Program
{
    static void Main()
    {
        Test t = new Test();
        t.Hello(); // Prints "From Test"
    }
}

Live-Demo zu .NET Fiddle


Wenn zwei Erweiterungsfunktionen mit derselben Signatur vorhanden sind und sich eine davon im selben Namespace befindet, wird diese mit Priorität versehen. Wenn jedoch auf beide über using zugegriffen wird, wird ein Fehler bei der Kompilierung mit der Nachricht angezeigt:

Der Aufruf ist zwischen den folgenden Methoden oder Eigenschaften mehrdeutig


Beachten Sie, dass der syntaktische Komfort des Aufrufs einer Erweiterungsmethode über originalTypeInstance.ExtensionMethod() ein optionaler Komfort ist. Die Methode kann auch auf herkömmliche Weise aufgerufen werden, so dass der spezielle erste Parameter als Parameter für die Methode verwendet wird.

Dh beide der folgenden Arbeiten:

//Calling as though method belongs to string--it seamlessly extends string
String s = "Hello World";
s.Shorten(5);  

//Calling as a traditional static method with two parameters
StringExtensions.Shorten(s, 5);

Explizite Verwendung einer Erweiterungsmethode

Erweiterungsmethoden können auch wie gewöhnliche statische Klassenmethoden verwendet werden. Diese Methode zum Aufrufen einer Erweiterungsmethode ist ausführlicher, jedoch in einigen Fällen erforderlich.

static class StringExtensions
{
    public static string Shorten(this string text, int length) 
    {
        return text.Substring(0, length);
    }
}

Verwendungszweck:

var newString = StringExtensions.Shorten("Hello World", 5);

Wann werden Erweiterungsmethoden als statische Methoden aufgerufen?

Es gibt immer noch Szenarien, in denen Sie eine Erweiterungsmethode als statische Methode verwenden müssen:

  • Konfliktlösung mit einer Member-Methode Dies kann passieren, wenn eine neue Version einer Bibliothek eine neue Member-Methode mit derselben Signatur einführt. In diesem Fall wird die Member-Methode vom Compiler bevorzugt.
  • Lösen von Konflikten mit einer anderen Erweiterungsmethode mit derselben Signatur. Dies kann passieren, wenn zwei Bibliotheken ähnliche Erweiterungsmethoden enthalten und Namespaces beider Klassen mit Erweiterungsmethoden in derselben Datei verwendet werden.
  • Übergabe der Erweiterungsmethode als Methodengruppe an den Delegatenparameter.
  • Machen Sie Ihre eigene Bindung durch Reflection .
  • Verwenden der Erweiterungsmethode im Direktfenster in Visual Studio.

Statisch verwenden

Wenn eine using static Direktive verwendet wird, um statische Member einer statischen Klasse in den globalen Bereich zu bringen, werden Erweiterungsmethoden übersprungen. Beispiel:

using static OurNamespace.StringExtensions; // refers to class in previous example

// OK: extension method syntax still works.
"Hello World".Shorten(5);
// OK: static method syntax still works.
OurNamespace.StringExtensions.Shorten("Hello World", 5);
// Compile time error: extension methods can't be called as static without specifying class.
Shorten("Hello World", 5);

Wenn Sie den Modifikator this aus dem ersten Argument der Shorten Methode entfernen, wird die letzte Zeile kompiliert.

Nullprüfung

Erweiterungsmethoden sind statische Methoden, die sich wie Instanzmethoden verhalten. Doch im Gegensatz zu, was passiert , wenn eine Instanz - Methode auf einer Aufruf null Referenz, wenn eine Erweiterungsmethode mit einer genannt wird null Referenz, macht es keinen werfen NullReferenceException . Dies kann in einigen Szenarien sehr nützlich sein.

Betrachten Sie beispielsweise die folgende statische Klasse:

public static class StringExtensions
{
    public static string EmptyIfNull(this string text)
    {
        return text ?? String.Empty;
    }

    public static string NullIfEmpty(this string text)
    {
        return String.Empty == text ? null : text;
    }
}
string nullString = null;
string emptyString = nullString.EmptyIfNull();// will return ""
string anotherNullString = emptyString.NullIfEmpty(); // will return null

Live-Demo zu .NET-Geige

Erweiterungsmethoden können nur öffentliche (oder interne) Mitglieder der erweiterten Klasse anzeigen

public class SomeClass
{
    public void DoStuff()
    {
        
    }

    protected void DoMagic()
    {
        
    }
}

public static class SomeClassExtensions
{
    public static void DoStuffWrapper(this SomeClass someInstance)
    {
        someInstance.DoStuff(); // ok
    }

    public static void DoMagicWrapper(this SomeClass someInstance)
    {
        someInstance.DoMagic(); // compilation error
    }
}

Erweiterungsmethoden sind nur ein syntaktischer Zucker und eigentlich keine Mitglieder der Klasse, die sie erweitern. Das bedeutet, dass sie die Kapselung nicht brechen können. Sie haben nur Zugriff auf public (oder wenn sie in derselben Assembly, internal ) Feldern, Eigenschaften und Methoden implementiert sind.

Generische Erweiterungsmethoden

Erweiterungsmethoden können wie andere Methoden Generika verwenden. Zum Beispiel:

static class Extensions
{
    public static bool HasMoreThanThreeElements<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Take(4).Count() > 3;
    }
}

und es wäre so zu nennen:

IEnumerable<int> numbers = new List<int> {1,2,3,4,5,6};
var hasMoreThanThreeElements = numbers.HasMoreThanThreeElements();

Demo anzeigen

Ebenso für mehrere Typargumente:

public static TU GenericExt<T, TU>(this T obj)
{
     TU ret = default(TU);
     // do some stuff with obj
     return ret;
}

Anrufen wäre wie folgt:

IEnumerable<int> numbers = new List<int> {1,2,3,4,5,6};
var result = numbers.GenericExt<IEnumerable<int>,String>();

Demo anzeigen

Sie können auch Erweiterungsmethoden für teilweise gebundene Typen in mehreren generischen Typen erstellen:

class MyType<T1, T2>
{
}

static class Extensions
{
    public static void Example<T>(this MyType<int, T> test)
    {        
    }
}

Anrufen wäre wie folgt:

MyType<int, string> t = new MyType<int, string>();
t.Example();

Demo anzeigen

Sie können auch Typeinschränkungen angeben, wobei where :

public static bool IsDefault<T>(this T obj) where T : struct, IEquatable<T>
{
     return EqualityComparer<T>.Default.Equals(obj, default(T));
}

Aufrufcode:

int number = 5;
var IsDefault = number.IsDefault();

Demo anzeigen

Versenden von Erweiterungsmethoden basierend auf statischem Typ

Der statische Typ (Kompilierzeit) wird anstelle des dynamischen Typs (Laufzeittyp) verwendet, um die Parameter abzugleichen.

public class Base 
{ 
    public virtual string GetName()
    {
        return "Base";
    }
}

public class Derived : Base
{ 
    public override string GetName()
    {
        return "Derived";
    }
}

public static class Extensions
{
    public static string GetNameByExtension(this Base item)
    {
        return "Base";
    }

    public static string GetNameByExtension(this Derived item)
    {
        return "Derived";
    }
}

public static class Program   
{
    public static void Main()
    {
        Derived derived = new Derived();
        Base @base = derived;

        // Use the instance method "GetName"
        Console.WriteLine(derived.GetName()); // Prints "Derived"
        Console.WriteLine(@base.GetName()); // Prints "Derived"

        // Use the static extension method "GetNameByExtension"
        Console.WriteLine(derived.GetNameByExtension()); // Prints "Derived"
        Console.WriteLine(@base.GetNameByExtension()); // Prints "Base"
    }
}

Live-Demo zu .NET-Geige

Der Versand, der auf einem statischen Typ basiert, erlaubt es nicht, eine Erweiterungsmethode für ein dynamic Objekt aufzurufen:

public class Person
{
    public string Name { get; set; }
}

public static class ExtenionPerson
{
    public static string GetPersonName(this Person person)
    {
        return person.Name;
    }
}

dynamic person = new Person { Name = "Jon" };
var name = person.GetPersonName(); // RuntimeBinderException is thrown

Erweiterungsmethoden werden von dynamischem Code nicht unterstützt.

static class Program
{
    static void Main()
    {
        dynamic dynamicObject = new ExpandoObject();

        string awesomeString = "Awesome";

        // Prints True
        Console.WriteLine(awesomeString.IsThisAwesome());

        dynamicObject.StringValue = awesomeString;

        // Prints True
        Console.WriteLine(StringExtensions.IsThisAwesome(dynamicObject.StringValue)); 
        
        // No compile time error or warning, but on runtime throws RuntimeBinderException
        Console.WriteLine(dynamicObject.StringValue.IsThisAwesome());
    }
}

static class StringExtensions
{
    public static bool IsThisAwesome(this string value)
    {
        return value.Equals("Awesome");
    }
}

Der Grund [der Aufruf von Erweiterungsmethoden aus dynamischem Code] funktioniert nicht, weil in regulären, nicht dynamischen Code-Erweiterungsmethoden eine vollständige Suche in allen dem Compiler bekannten Klassen nach einer statischen Klasse mit einer entsprechenden Erweiterungsmethode durchgeführt wird . Die Suche wird basierend auf der Namespace-Verschachtelung in der Reihenfolge ausgeführt und ist using Direktiven in jedem Namespace verfügbar.

Das bedeutet , dass , um einen dynamischen Erweiterung Methodenaufruf korrekt aufgelöst zu bekommen, irgendwie das DLR zur Laufzeit wissen hat , was alle Namespace Verschachtelungen und using Direktiven im Quellcode waren. Wir verfügen nicht über einen praktischen Mechanismus, um all diese Informationen in die Anrufseite zu kodieren. Wir haben überlegt, einen solchen Mechanismus zu erfinden, entschieden uns jedoch für zu hohe Kosten und ein zu großes Zeitplanrisiko, um es sich zu lohnen.

Quelle

Erweiterungsmethoden als stark typisierte Wrapper

Erweiterungsmethoden können zum Schreiben stark typisierter Wrapper für wörterbuchartige Objekte verwendet werden. Zum Beispiel einen Cache, HttpContext.Items at cetera ...

public static class CacheExtensions
{
    public static void SetUserInfo(this Cache cache, UserInfo data) => 
        cache["UserInfo"] = data;

    public static UserInfo GetUserInfo(this Cache cache) => 
        cache["UserInfo"] as UserInfo;
}

Dieser Ansatz beseitigt die Notwendigkeit, String-Literale als Schlüssel in der gesamten Codebase zu verwenden, und es ist auch nicht erforderlich, während des Lesevorgangs auf den erforderlichen Typ zu gießen. Insgesamt wird eine sicherere, stark typisierte Art der Interaktion mit derart lose typisierten Objekten wie Wörterbüchern geschaffen.

Erweiterungsmethoden für die Verkettung

Wenn eine Erweiterungsmethode einen Wert zurückgibt, der denselben Typ wie this Argument hat, kann sie verwendet werden, um einen oder mehrere Methodenaufrufe mit einer kompatiblen Signatur zu "verketten". Dies kann für versiegelte und / oder primitive Typen nützlich sein und ermöglicht die Erstellung sogenannter "fließender" APIs, wenn die Methodennamen wie natürliche menschliche Sprache gelesen werden.

void Main()
{
    int result = 5.Increment().Decrement().Increment(); 
    // result is now 6
}

public static class IntExtensions 
{
    public static int Increment(this int number) {
        return ++number;
    }

    public static int Decrement(this int number) {
        return --number;
    }
}

Oder so

void Main()
{
    int[] ints = new[] { 1, 2, 3, 4, 5, 6};
    int[] a = ints.WhereEven();
    //a is { 2, 4, 6 };
    int[] b = ints.WhereEven().WhereGreaterThan(2);
    //b is { 4, 6 };
}

public static class IntArrayExtensions
{
    public static int[] WhereEven(this int[] array)
    {
        //Enumerable.* extension methods use a fluent approach
        return array.Where(i => (i%2) == 0).ToArray();
    }

    public static int[] WhereGreaterThan(this int[] array, int value)
    {
        return array.Where(i => i > value).ToArray();
    }
}

Erweiterungsmethoden in Kombination mit Schnittstellen

Die Verwendung von Erweiterungsmethoden mit Schnittstellen ist sehr praktisch, da die Implementierung außerhalb der Klasse gespeichert werden kann. Alles, was zur Erweiterung der Klasse erforderlich ist, ist das Dekorieren der Klasse mit Schnittstelle.

public interface IInterface
{
   string Do()
}

public static class ExtensionMethods{
    public static string DoWith(this IInterface obj){
      //does something with IInterface instance
    }
}

public class Classy : IInterface
{
   // this is a wrapper method; you could also call DoWith() on a Classy instance directly,
   // provided you import the namespace containing the extension method
   public Do(){
       return this.DoWith();
   }
}

Verwenden Sie wie:

 var classy = new Classy();
 classy.Do(); // will call the extension
 classy.DoWith(); // Classy implements IInterface so it can also be called this way

IList Erweiterungsmethode - Beispiel: Vergleich von 2 Listen

Sie können die folgende Erweiterungsmethode verwenden, um den Inhalt von zwei IList <T> -Instanzen desselben Typs zu vergleichen.

Standardmäßig werden die Elemente basierend auf ihrer Reihenfolge in der Liste und den Elementen selbst verglichen. isOrdered false an den Parameter isOrdered , werden nur die Elemente selbst unabhängig von ihrer Reihenfolge verglichen.

Für diese Methode funktioniert, der generische Typ ( T müssen beide außer Kraft setzen) Equals und GetHashCode Methoden.

Verwendungszweck:

List<string> list1 = new List<string> {"a1", "a2", null, "a3"};
List<string> list2 = new List<string> {"a1", "a2", "a3", null};

list1.Compare(list2);//this gives false
list1.Compare(list2, false);//this gives true. they are equal when the order is disregarded

Methode:

public static bool Compare<T>(this IList<T> list1, IList<T> list2, bool isOrdered = true) 
{
    if (list1 == null && list2 == null)
        return true;
    if (list1 == null || list2 == null || list1.Count != list2.Count)
        return false;

    if (isOrdered)
    {
        for (int i = 0; i < list2.Count; i++)
        {
            var l1 = list1[i]; 
            var l2 = list2[i];
            if (
                 (l1 == null && l2 != null) || 
                 (l1 != null && l2 == null) || 
                 (!l1.Equals(l2)))
            {
                    return false;
            }
        }
        return true;
    }
    else
    {
        List<T> list2Copy = new List<T>(list2);
        //Can be done with Dictionary without O(n^2)
        for (int i = 0; i < list1.Count; i++)
        {
            if (!list2Copy.Remove(list1[i]))
                return false;
        }
        return true;
    }
}

Erweiterungsmethoden mit Enumeration

Erweiterungsmethoden sind nützlich, um Aufzählungen Funktionalität hinzuzufügen.

Eine übliche Verwendung ist die Implementierung einer Konvertierungsmethode.

public enum YesNo
{
    Yes,
    No,
}

public static class EnumExtentions
{
    public static bool ToBool(this YesNo yn)
    {
        return yn == YesNo.Yes;
    }
    public static YesNo ToYesNo(this bool yn)
    {
        return yn ? YesNo.Yes : YesNo.No;
    }
}

Jetzt können Sie Ihren Aufzählungswert schnell in einen anderen Typ konvertieren. In diesem Fall ein Bool.

bool yesNoBool = YesNo.Yes.ToBool(); // yesNoBool == true
YesNo yesNoEnum = false.ToYesNo();   // yesNoEnum == YesNo.No

Alternativ können Erweiterungsmethoden verwendet werden, um eigenschaftsähnliche Methoden hinzuzufügen.

public enum Element
{
    Hydrogen,
    Helium,
    Lithium,
    Beryllium,
    Boron,
    Carbon,
    Nitrogen,
    Oxygen
    //Etc
}

public static class ElementExtensions
{
    public static double AtomicMass(this Element element)
    {
        switch(element)
        {
            case Element.Hydrogen:  return 1.00794;
            case Element.Helium:    return 4.002602;
            case Element.Lithium:   return 6.941;
            case Element.Beryllium: return 9.012182;
            case Element.Boron:     return 10.811;
            case Element.Carbon:    return 12.0107;
            case Element.Nitrogen:  return 14.0067;
            case Element.Oxygen:    return 15.9994;
            //Etc
        }
        return double.Nan;
    }
}

var massWater = 2*Element.Hydrogen.AtomicMass() + Element.Oxygen.AtomicMass();

Erweiterungen und Schnittstellen ermöglichen zusammen DRY-Code und Mixin-ähnliche Funktionalität

Erweiterungsmethoden ermöglichen es Ihnen, Ihre Schnittstellendefinitionen zu vereinfachen, indem Sie nur die erforderlichen Kernfunktionen in die Schnittstelle selbst aufnehmen und als Erweiterungsmethoden Komfortmethoden und Überladungen definieren. Schnittstellen mit weniger Methoden lassen sich in neuen Klassen leichter implementieren. Durch das Beibehalten von Überladungen als Erweiterungen, anstatt sie in die Benutzeroberfläche aufzunehmen, müssen Sie den Boilerplate-Code nicht direkt in jede Implementierung kopieren. Dies ist in der Tat dem Mischmuster ähnlich, das C # nicht unterstützt.

System.Linq.Enumerable ‚s Erweiterungen IEnumerable<T> ist ein gutes Beispiel dafür. IEnumerable<T> muss die implementierende Klasse nur zwei Methoden implementieren: generische und nicht generische GetEnumerator() . System.Linq.Enumerable bietet jedoch unzählige nützliche Dienstprogramme als Erweiterungen, die eine übersichtliche und klare Verwendung von IEnumerable<T> .

Im Folgenden finden Sie eine sehr einfache Schnittstelle, die als Erweiterungen nützliche Überlastungen bietet.

public interface ITimeFormatter
{
   string Format(TimeSpan span);
}

public static class TimeFormatter
{
    // Provide an overload to *all* implementers of ITimeFormatter.
    public static string Format(
        this ITimeFormatter formatter,
        int millisecondsSpan)
        => formatter.Format(TimeSpan.FromMilliseconds(millisecondsSpan));
}

// Implementations only need to provide one method. Very easy to
// write additional implementations.
public class SecondsTimeFormatter : ITimeFormatter
{
   public string Format(TimeSpan span)
   {
       return $"{(int)span.TotalSeconds}s";
   }
}

class Program
{
    static void Main(string[] args)
    {
        var formatter = new SecondsTimeFormatter();
        // Callers get two method overloads!
        Console.WriteLine($"4500ms is rougly {formatter.Format(4500)}");
        var span = TimeSpan.FromSeconds(5);
        Console.WriteLine($"{span} is formatted as {formatter.Format(span)}");
    }
}

Erweiterungsmethoden für die Behandlung von Sonderfällen

Erweiterungsmethoden können verwendet werden, um die Verarbeitung ineleganter Geschäftsregeln "zu verbergen", die andernfalls eine aufrufende Funktion mit if / then-Anweisungen überladen würden. Dies ist ähnlich und vergleichbar mit dem Umgang mit Nullwerten mit Erweiterungsmethoden. Zum Beispiel,

public static class CakeExtensions
{
    public static Cake EnsureTrueCake(this Cake cake)
    {
        //If the cake is a lie, substitute a cake from grandma, whose cakes aren't as tasty but are known never to be lies. If the cake isn't a lie, don't do anything and return it.
        return CakeVerificationService.IsCakeLie(cake) ? GrandmasKitchen.Get1950sCake() : cake;
    }
}
Cake myCake = Bakery.GetNextCake().EnsureTrueCake();
myMouth.Eat(myCake);//Eat the cake, confident that it is not a lie.

Verwenden von Erweiterungsmethoden mit statischen Methoden und Rückrufen

Erwägen Sie die Verwendung von Erweiterungsmethoden als Funktionen, die anderen Code umschließen. Hier ein gutes Beispiel, das sowohl eine statische als auch eine Erweiterungsmethode verwendet, um das Try Catch-Konstrukt zu umschließen. Machen Sie Ihren Code Bullet Proof ...

using System;
using System.Diagnostics;

namespace Samples
{
    /// <summary>
    /// Wraps a try catch statement as a static helper which uses 
    /// Extension methods for the exception
    /// </summary>
    public static class Bullet
    {
        /// <summary>
        /// Wrapper for Try Catch Statement
        /// </summary>
        /// <param name="code">Call back for code</param>
        /// <param name="error">Already handled and logged exception</param>
        public static void Proof(Action code, Action<Exception> error)
        {
            try
            {
                code();
            }
            catch (Exception iox)
            {
                //extension method used here
                iox.Log("BP2200-ERR-Unexpected Error");
                //callback, exception already handled and logged
                error(iox);
            }
        }
        /// <summary>
        /// Example of a logging method helper, this is the extension method
        /// </summary>
        /// <param name="error">The Exception to log</param>
        /// <param name="messageID">A unique error ID header</param>
        public static void Log(this Exception error, string messageID)
        {
            Trace.WriteLine(messageID);
            Trace.WriteLine(error.Message);
            Trace.WriteLine(error.StackTrace);
            Trace.WriteLine("");
        }
    }
    /// <summary>
    /// Shows how to use both the wrapper and extension methods.
    /// </summary>
    public class UseBulletProofing
    {
        public UseBulletProofing()
        {
            var ok = false;
            var result = DoSomething();
            if (!result.Contains("ERR"))
            {
                ok = true;
                DoSomethingElse();
            }
        }

        /// <summary>
        /// How to use Bullet Proofing in your code.
        /// </summary>
        /// <returns>A string</returns>
        public string DoSomething()
        {
            string result = string.Empty;
            //Note that the Bullet.Proof method forces this construct.
            Bullet.Proof(() =>
            {
                //this is the code callback
                result = "DST5900-INF-No Exceptions in this code";
            }, error =>
            {
                //error is the already logged and handled exception
                //determine the base result
                result = "DTS6200-ERR-An exception happened look at console log";
                if (error.Message.Contains("SomeMarker"))
                {
                    //filter the result for Something within the exception message
                    result = "DST6500-ERR-Some marker was found in the exception";
                }
            });
            return result;
        }

        /// <summary>
        /// Next step in workflow
        /// </summary>
        public void DoSomethingElse()
        {
            //Only called if no exception was thrown before
        }
    }
}

Erweiterungsmethoden für Interfaces

Eine nützliche Funktion von Erweiterungsmethoden ist, dass Sie gängige Methoden für eine Schnittstelle erstellen können. Normalerweise kann eine Schnittstelle keine gemeinsamen Implementierungen haben, mit Erweiterungsmethoden jedoch.

public interface IVehicle
{
    int MilesDriven { get; set; }
}

public static class Extensions
{
    public static int FeetDriven(this IVehicle vehicle)
    {
        return vehicle.MilesDriven * 5028;
    }
}

In diesem Beispiel kann die Methode FeetDriven für jedes IVehicle FeetDriven verwendet werden. Diese Logik in dieser Methode würde für alle IVehicle gelten, so dass dies auf diese Weise geschehen kann, so dass es in der IVehicle Definition keinen FeetDriven IVehicle muss, der für alle Kinder gleich implementiert wäre.

Verwenden von Erweiterungsmethoden zum Erstellen schöner Mapper-Klassen

Wir können bessere Mapper-Klassen mit Erweiterungsmethoden erstellen. Angenommen, ich habe einige DTO-Klassen wie

 public class UserDTO
 {
        public AddressDTO Address { get; set; }
 }

 public class AddressDTO
 {
        public string Name { get; set; }
 }

und ich muss entsprechende Ansichtsmodellklassen zuordnen

public class UserViewModel
{
    public AddressViewModel Address { get; set; }
}

public class AddressViewModel
{
    public string Name { get; set; }
}

Dann kann ich meine Mapper-Klasse wie folgt erstellen

public static class ViewModelMapper
{
      public static UserViewModel ToViewModel(this UserDTO user)
      {
            return user == null ?
                null :
                new UserViewModel()
                {
                    Address = user.Address.ToViewModel()
                    // Job = user.Job.ToViewModel(),
                    // Contact = user.Contact.ToViewModel() .. and so on
                };
      }

      public static AddressViewModel ToViewModel(this AddressDTO userAddr)
      {
            return userAddr == null ?
                null :
                new AddressViewModel()
                {
                    Name = userAddr.Name
                };
      }
}

Dann kann ich meinen Mapper wie folgt aufrufen

    UserDTO userDTOObj = new UserDTO() {
            Address = new AddressDTO() {
                Name = "Address of the user"
            }
        };

    UserViewModel user = userDTOObj.ToViewModel(); // My DTO mapped to Viewmodel

Das Schöne daran ist, dass alle Zuordnungsmethoden einen gemeinsamen Namen haben (ToViewModel), und wir können sie auf verschiedene Weise wiederverwenden

Verwenden von Erweiterungsmethoden zum Erstellen neuer Auflistungstypen (z. B. DictList)

Sie können Erweiterungsmethoden erstellen, um die Verwendbarkeit für verschachtelte Sammlungen wie ein Dictionary mit einem List<T> zu verbessern.

Berücksichtigen Sie die folgenden Erweiterungsmethoden:

public static class DictListExtensions
{
    public static void Add<TKey, TValue, TCollection>(this Dictionary<TKey, TCollection> dict, TKey key, TValue value)
            where TCollection : ICollection<TValue>, new()
    {
        TCollection list;
        if (!dict.TryGetValue(key, out list))
        {
            list = new TCollection();
            dict.Add(key, list);
        }

        list.Add(value);
    }

    public static bool Remove<TKey, TValue, TCollection>(this Dictionary<TKey, TCollection> dict, TKey key, TValue value)
        where TCollection : ICollection<TValue>
    {
        TCollection list;
        if (!dict.TryGetValue(key, out list))
        {
            return false;
        }

        var ret = list.Remove(value);
        if (list.Count == 0)
        {
            dict.Remove(key);
        }
        return ret;
    }
}

Sie können die Erweiterungsmethoden wie folgt verwenden:

var dictList = new Dictionary<string, List<int>>();

dictList.Add("example", 5);
dictList.Add("example", 10);
dictList.Add("example", 15);

Console.WriteLine(String.Join(", ", dictList["example"])); // 5, 10, 15

dictList.Remove("example", 5);
dictList.Remove("example", 10);

Console.WriteLine(String.Join(", ", dictList["example"])); // 15

dictList.Remove("example", 15);

Console.WriteLine(dictList.ContainsKey("example")); // False

Demo anzeigen



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow