Zoeken…


Syntaxis

  • public static ReturnType MyExtensionMethod (dit TargetType-doel)
  • public static ReturnType MyExtensionMethod (dit TargetType-doel, TArg1 arg1, ...)

parameters

Parameter Details
deze De eerste parameter van een uitbreidingsmethode moet altijd worden voorafgegaan door het trefwoord this , gevolgd door de identificatie waarmee wordt verwezen naar de "huidige" instantie van het object dat u uitbreidt

Opmerkingen

Uitbreidingsmethoden zijn syntactische suiker waarmee statische methoden kunnen worden aangeroepen op objectexemplaren alsof ze lid zijn van het type zelf.

Uitbreidingsmethoden vereisen een expliciet doelobject. U moet het trefwoord this om toegang te krijgen tot de methode vanuit het uitgebreide type zelf.

Extensiemethoden moeten statisch worden verklaard en moeten in een statische klasse leven.

Welke naamruimte?

De keuze van de naamruimte voor uw extensiemethodeklasse is een afweging tussen zichtbaarheid en vindbaarheid.

De meest genoemde optie is om een aangepaste naamruimte te hebben voor uw extensiemethoden. Dit vereist echter communicatie-inspanningen, zodat gebruikers van uw code weten dat de uitbreidingsmethoden bestaan en waar ze deze kunnen vinden.

Een alternatief is om een naamruimte te kiezen zodat ontwikkelaars uw extensiemethoden via Intellisense zullen ontdekken. Dus als u de Foo klasse wilt uitbreiden, is het logisch om de uitbreidingsmethoden in dezelfde naamruimte te plaatsen als Foo .

Het is belangrijk om te beseffen dat niets u verhindert om de naamruimte van "iemand anders" te gebruiken : dus als u IEnumerable wilt uitbreiden, kunt u uw uitbreidingsmethode toevoegen in de naamruimte System.Linq .

Dit is niet altijd een goed idee. In één specifiek geval wilt u bijvoorbeeld een gemeenschappelijk type uitbreiden ( bool IsApproxEqualTo(this double value, double other) bijvoorbeeld), maar niet dat het hele System 'vervuilen'. In dit geval verdient het de voorkeur om een lokale, specifieke naamruimte te kiezen.

Ten slotte is het ook mogelijk om de uitbreidingsmethoden helemaal niet in een naamruimte te plaatsen !

Een goede referentievraag: hoe beheert u de naamruimten van uw extensiemethoden?

toepasselijkheid

Bij het maken van uitbreidingsmethoden moet ervoor worden gezorgd dat deze geschikt zijn voor alle mogelijke invoer en niet alleen relevant zijn voor specifieke situaties. Het is bijvoorbeeld mogelijk om systeemklassen zoals string te breiden, waardoor uw nieuwe code beschikbaar is voor elke tekenreeks. Als uw code domeinspecifieke logica op een domeinspecifiek stringformaat moet uitvoeren, zou een extensiemethode niet geschikt zijn omdat de aanwezigheid ervan bellers zou kunnen verwarren die met andere strings in het systeem werken.

De volgende lijst bevat basisfuncties en eigenschappen van uitbreidingsmethoden

  1. Het moet een statische methode zijn.
  2. Het moet zich in een statische klasse bevinden.
  3. Het gebruikt het trefwoord "this" als de eerste parameter met een type in .NET en deze methode wordt aan de clientzijde aangeroepen door een bepaald type instantie.
  4. Het wordt ook getoond door VS intellisense. Wanneer we op de stip drukken . na een typevoorziening komt het in VS intellisense.
  5. Een extensiemethode moet zich in dezelfde naamruimte bevinden als waarin deze wordt gebruikt of u moet de naamruimte van de klasse importeren met een instructie statement.
  6. U kunt elke naam voor de klasse opgeven die een extensiemethode heeft, maar de klasse moet statisch zijn.
  7. Als u nieuwe methoden aan een type wilt toevoegen en u hebt er niet de broncode voor, dan is de oplossing om extensiemethoden van dat type te gebruiken en te implementeren.
  8. Als u uitbreidingsmethoden maakt die dezelfde handtekeningmethoden hebben als het type dat u uitbreidt, worden de uitbreidingsmethoden nooit aangeroepen.

Uitbreidingsmethoden - overzicht

Uitbreidingsmethoden werden geïntroduceerd in C # 3.0. Uitbreidingsmethoden breiden uit en voegen gedrag toe aan bestaande typen zonder een nieuw afgeleid type te maken, het nieuwe type opnieuw te compileren of anderszins te wijzigen. Ze zijn vooral handig als u de bron van een type dat u wilt verbeteren niet kunt wijzigen. Uitbreidingsmethoden kunnen worden gemaakt voor systeemtypen, typen die zijn gedefinieerd door derden en typen die u zelf hebt gedefinieerd. De uitbreidingsmethode kan worden opgeroepen alsof het een lidmethode van het oorspronkelijke type is. Hierdoor kan Method Chaining worden gebruikt om een vloeiende interface te implementeren.

Een uitbreidingsmethode wordt gemaakt door een statische methode toe te voegen aan een statische klasse die verschilt van het oorspronkelijke type dat wordt uitgebreid. De statische klasse die de extensiemethode bevat, wordt vaak gemaakt met als enige doel extensiemethoden vast te houden.

Uitbreidingsmethoden hebben een speciale eerste parameter die aangeeft welk origineel type wordt uitgebreid. Deze eerste parameter is versierd met het trefwoord this (wat een speciaal en apart gebruik this in C # vormt - het moet worden opgevat als anders dan het gebruik this dat verwijst naar leden van de huidige objectinstantie).

In het volgende voorbeeld is het oorspronkelijke type wordt uitgebreid is de klasse string . String is uitgebreid met een methode Shorten() , die de extra functionaliteit van verkorten biedt. De statische klasse StringExtensions is gemaakt om de uitbreidingsmethode te bevatten. De uitbreidingsmethode Shorten() laat zien dat het een extensie van een string via de speciaal gemarkeerde eerste parameter. Aantonen dat het Shorten() methode uitstrekt string wordt de eerste parameter met this . Daarom is de volledige handtekening van de eerste parameter this string text , waarbij string het oorspronkelijke type is dat wordt uitgebreid en text de gekozen parameternaam is.

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 op .NET Fiddle


Het object dat wordt doorgegeven als het eerste argument van een uitbreidingsmethode (die gepaard gaat met het trefwoord this ) is de instantie waarvoor de uitbreidingsmethode wordt gebruikt.

Wanneer deze code bijvoorbeeld wordt uitgevoerd:

"some string".Shorten(5);

De waarden van de argumenten zijn als volgt:

text: "some string"
length: 5

Merk op dat extensiemethoden alleen bruikbaar zijn als ze zich in dezelfde naamruimte bevinden als hun definitie, als de naamruimte expliciet wordt geïmporteerd door de code met behulp van de extensiemethode, of als de extensieklasse geen naamruimte heeft. De .NET framework-richtlijnen bevelen aan om extensieklassen in hun eigen naamruimte te plaatsen. Dit kan echter leiden tot detectieproblemen.

Dit resulteert in geen conflicten tussen de uitbreidingsmethoden en de bibliotheken die worden gebruikt, tenzij naamruimten die mogelijk een conflict veroorzaken expliciet worden ingetrokken. Bijvoorbeeld LINQ Extensies :

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 op .NET Fiddle


Sinds C # 6.0 is het ook mogelijk om een using static richtlijn te gebruiken voor de klasse die de uitbreidingsmethoden bevat. Bijvoorbeeld using static System.Linq.Enumerable; . Dit maakt uitbreidingsmethoden uit die bepaalde klasse beschikbaar zonder andere typen uit dezelfde naamruimte in het bereik te brengen.


Wanneer een klassemethode met dezelfde handtekening beschikbaar is, geeft de compiler deze prioriteit boven de aanroep van de extensiemethode. Bijvoorbeeld:

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 op .NET Fiddle


Merk op dat als er twee uitbreidingsfuncties met dezelfde handtekening zijn en een ervan zich in dezelfde naamruimte bevindt, deze prioriteit krijgt. Aan de andere kant, als beide met using van worden benaderd, volgt een compilatietijdfout met het bericht:

De aanroep is dubbelzinnig tussen de volgende methoden of eigenschappen


Merk op dat het syntactische gemak van het aanroepen van een uitbreidingsmethode via originalTypeInstance.ExtensionMethod() een optioneel gemak is. De methode kan ook op de traditionele manier worden aangeroepen, zodat de speciale eerste parameter wordt gebruikt als parameter voor de methode.

Dat wil zeggen, beide van de volgende werkzaamheden:

//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);

Expliciet met behulp van een uitbreidingsmethode

Uitbreidingsmethoden kunnen ook worden gebruikt zoals gewone methoden van statische klasse. Deze manier om een extensiemethode aan te roepen is uitgebreider, maar is in sommige gevallen noodzakelijk.

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

Gebruik:

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

Wanneer extensiemethoden als statische methoden aanroepen

Er zijn nog steeds scenario's waarin u een extensiemethode als statische methode zou moeten gebruiken:

  • Conflict oplossen met een lidmethode. Dit kan gebeuren als een nieuwe versie van een bibliotheek een nieuwe lidmethode met dezelfde handtekening introduceert. In dit geval heeft de compiler de voorkeur voor de lidmethode.
  • Conflicten oplossen met een andere uitbreidingsmethode met dezelfde handtekening. Dit kan gebeuren als twee bibliotheken vergelijkbare extensiemethoden bevatten en naamruimten van beide klassen met extensiemethoden in hetzelfde bestand worden gebruikt.
  • Extensiemethode doorgeven als een methodegroep in de parameter delegeren.
  • Je eigen binding doen door Reflection .
  • De uitbreidingsmethode gebruiken in het venster Direct in Visual Studio.

Statisch gebruiken

Als een using static richtlijn wordt gebruikt om statische leden van een statische klasse in een wereldwijd bereik te brengen, worden uitbreidingsmethoden overgeslagen. Voorbeeld:

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);

Als u this modifier uit het eerste argument van de methode Shorten , wordt de laatste regel gecompileerd.

Controle op nul

Uitbreidingsmethoden zijn statische methoden die zich gedragen als instantiemethoden. Echter, in tegenstelling tot wat er gebeurt bij het aanroepen van een instantie methode op een null referentie, wanneer een uitbreiding methode wordt aangeroepen met een null referentie, is het niet een gooien NullReferenceException . Dit kan in sommige scenario's behoorlijk handig zijn.

Overweeg bijvoorbeeld de volgende 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 op .NET Fiddle

Uitbreidingsmethoden kunnen alleen openbare (of interne) leden van de uitgebreide klasse zien

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
    }
}

Uitbreidingsmethoden zijn slechts een syntactische suiker, en zijn eigenlijk geen leden van de klasse die ze uitbreiden. Dit betekent dat ze inkapseling niet kunnen verbreken - ze hebben alleen toegang tot public (of wanneer geïmplementeerd in dezelfde assemblage, internal ) velden, eigenschappen en methoden.

Algemene uitbreidingsmethoden

Net als andere methoden, kunnen uitbreidingsmethoden generieken gebruiken. Bijvoorbeeld:

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

en het noemen zou zo zijn:

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

Demo bekijken

Evenzo voor meerdere Type Argumenten:

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

Het noemen zou zo zijn:

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

Demo bekijken

U kunt ook uitbreidingsmethoden maken voor gedeeltelijk gebonden typen in meerdere generieke typen:

class MyType<T1, T2>
{
}

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

Het noemen zou zo zijn:

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

Demo bekijken

U kunt ook opgeven soort beperkingen met where :

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

Oproepcode:

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

Demo bekijken

Uitbreidingsmethoden verzending op basis van statisch type

Het statische (compilatie) type wordt gebruikt in plaats van het dynamische (runtime type) om parameters te matchen.

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 op .NET Fiddle

Ook staat de verzending op basis van het statische type niet toe dat een uitbreidingsmethode wordt aangeroepen voor een dynamic object:

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

Extensiemethoden worden niet ondersteund door dynamische code.

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");
    }
}

De reden [het aanroepen van uitbreidingsmethoden vanuit dynamische code] werkt niet omdat in reguliere, niet-dynamische code-uitbreidingsmethoden werkt door een volledige zoekopdracht uit te voeren in alle klassen die de compiler kent voor een statische klasse met een uitbreidingsmethode die overeenkomt . De zoekopdracht verloopt in volgorde op basis van de naamruimte-nesting en beschikbaar using richtlijnen in elke naamruimte.

Dat betekent dat om een dynamische uitbreidingsmethode-aanroep correct te laten oplossen, de DLR op de een of andere manier tijdens runtime moet weten wat alle naamruimte-nestings en het using richtlijnen in uw broncode waren . We hebben geen handig mechanisme om al die informatie in de oproepsite te coderen. We hebben overwogen een dergelijk mechanisme uit te vinden, maar besloten dat het te hoge kosten was en te veel planningsrisico opleverde om het waard te zijn.

Bron

Uitbreidingsmethoden als sterk getypte omslagen

Uitbreidingsmethoden kunnen worden gebruikt voor het schrijven van sterk getypte omslagen voor woordenboekachtige objecten. Bijvoorbeeld een cache, HttpContext.Items bij 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;
}

Met deze benadering is het niet langer nodig om stringliterals als sleutels overal in de codebase te gebruiken, evenals de noodzaak om tijdens het lezen naar het gewenste type te casten. Over het algemeen creëert het een veiligere, sterk getypeerde manier van interactie met losjes getypte objecten als Woordenboeken.

Uitbreidingsmethoden voor kettingvorming

Wanneer een extensiemethode een waarde retourneert die van hetzelfde type is als this argument, kan deze worden gebruikt om een of meer methodeaanroepen met een compatibele handtekening te "ketenen". Dit kan handig zijn voor gesloten en / of primitieve typen en maakt het mogelijk om zogenaamde "vloeiende" API's te maken als de methodenamen als natuurlijke menselijke taal lezen.

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;
    }
}

Of zo

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();
    }
}

Uitbreidingsmethoden in combinatie met interfaces

Het is erg handig om uitbreidingsmethoden met interfaces te gebruiken, omdat de implementatie buiten de klas kan worden opgeslagen en het enige dat nodig is om enige functionaliteit aan de klas toe te voegen, is om de klas te decoreren met de interface.

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();
   }
}

gebruik als:

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

IList Uitbreidingsmethode Voorbeeld: vergelijking van 2 lijsten

U kunt de volgende uitbreidingsmethode gebruiken om de inhoud van twee IList <T> -instances van hetzelfde type te vergelijken.

Standaard worden de items vergeleken op basis van hun volgorde in de lijst en de items zelf, waarbij false wordt doorgegeven aan de parameter isOrdered , worden alleen de items zelf vergeleken, ongeacht hun volgorde.

Deze methode werkt alleen als het generieke type ( T ) beide methoden Equals en GetHashCode overschrijft.

Gebruik:

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;
    }
}

Uitbreidingsmethoden met opsomming

Uitbreidingsmethoden zijn handig voor het toevoegen van functionaliteit aan opsommingen.

Een veel voorkomend gebruik is het implementeren van een conversiemethode.

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;
    }
}

Nu kunt u uw opsommingswaarde snel naar een ander type converteren. In dit geval een bool.

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

Als alternatief kunnen uitbreidingsmethoden worden gebruikt om eigenschappen zoals methoden toe te voegen.

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();

Uitbreidingen en interfaces maken samen DRY-code en mixin-achtige functionaliteit mogelijk

Met uitbreidingsmethoden kunt u uw interfacedefinities vereenvoudigen door alleen de vereiste kernfunctionaliteit in de interface zelf op te nemen en kunt u gemaksmethoden en overbelastingen als uitbreidingsmethoden definiëren. Interfaces met minder methoden zijn gemakkelijker te implementeren in nieuwe klassen. Door overbelastingen als extensies te houden in plaats van ze op te nemen in de interface, bespaart u direct van het kopiëren van boilerplate-code naar elke implementatie, waardoor u uw code DROOG kunt houden. Dit is in feite vergelijkbaar met het mixin-patroon dat C # niet ondersteunt.

De extensies van System.Linq.Enumerable voor IEnumerable<T> zijn hier een goed voorbeeld van. IEnumerable<T> vereist alleen dat de implementatieklasse twee methoden implementeert: generieke en niet-generieke GetEnumerator() . Maar System.Linq.Enumerable biedt talloze nuttige hulpprogramma's als extensies die een beknopt en duidelijk verbruik van IEnumerable<T> .

Het volgende is een zeer eenvoudige interface met handige overbelastingen die als extensies worden aangeboden.

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)}");
    }
}

Uitbreidingsmethoden voor het behandelen van speciale gevallen

Uitbreidingsmethoden kunnen worden gebruikt om de verwerking van niet-legale bedrijfsregels te "verbergen" die anders een rommelige functie met if / then-verklaringen zou vereisen. Dit is vergelijkbaar met en analoog aan het omgaan met nullen met uitbreidingsmethoden. Bijvoorbeeld,

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.

Uitbreidingsmethoden gebruiken met statische methoden en callbacks

Overweeg het gebruik van extensiemethoden als functies die andere code verpakken, hier is een geweldig voorbeeld dat zowel een statische methode als een extensiemethode gebruikt om het Try Catch-construct te verpakken. Maak uw 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
        }
    }
}

Uitbreidingsmethoden op interfaces

Een handige functie van uitbreidingsmethoden is dat u algemene methoden voor een interface kunt maken. Normaal gesproken kan een interface geen gedeelde implementaties hebben, maar met uitbreidingsmethoden wel.

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

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

In dit voorbeeld kan de methode FeetDriven op elk IVehicle worden gebruikt. Deze logica in deze methode zou van toepassing zijn op alle IVehicle 's, dus het kan op deze manier worden gedaan zodat er geen FeetDriven in de IVehicle definitie hoeft te zijn die op dezelfde manier voor alle kinderen zou worden geïmplementeerd.

Uitbreidingsmethoden gebruiken om prachtige mapper-klassen te maken

We kunnen betere mapper-klassen maken met uitbreidingsmethoden, stel dat ik sommige DTO-klassen zoals heb

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

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

en ik moet toewijzen aan overeenkomstige weergave modelklassen

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

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

dan kan ik mijn mapper class maken zoals hieronder

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
                };
      }
}

Dan kan ik eindelijk mijn mapper aanroepen zoals hieronder

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

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

Het mooie is dat alle toewijzingsmethoden een gemeenschappelijke naam hebben (ToViewModel) en we kunnen deze op verschillende manieren hergebruiken

Uitbreidingsmethoden gebruiken om nieuwe collectietypen te maken (bijv. DictList)

U kunt extensiemethoden maken om de bruikbaarheid voor geneste collecties te verbeteren, zoals een Dictionary met de waarde List<T> .

Overweeg de volgende uitbreidingsmethoden:

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;
    }
}

u kunt de uitbreidingsmethoden als volgt gebruiken:

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 bekijken



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow