Szukaj…


Składnia

  • public static ReturnType MyExtensionMethod (ten cel TargetType)
  • public static ReturnType MyExtensionMethod (ten cel TargetType, TArg1 arg1, ...)

Parametry

Parametr Detale
to Pierwszy parametr metody rozszerzenia powinien zawsze poprzedzać this słowo kluczowe, a następnie identyfikator, za pomocą którego można odnosić się do „bieżącej” instancji rozszerzanego obiektu

Uwagi

Metody rozszerzeń to cukier składniowy, który umożliwia wywoływanie metod statycznych w instancjach obiektów, tak jakby były one składnikiem samego typu.

Metody rozszerzenia wymagają jawnego obiektu docelowego. Musisz użyć this słowa kluczowego, aby uzyskać dostęp do metody z samego rozszerzonego typu.

Metody rozszerzeń muszą być zadeklarowane jako statyczne i muszą żyć w klasie statycznej.

Która przestrzeń nazw?

Wybór przestrzeni nazw dla klasy metody rozszerzenia jest kompromisem między widocznością a wykrywalnością.

Najczęściej wymienianą opcją jest posiadanie niestandardowej przestrzeni nazw dla metod rozszerzenia. Będzie to jednak wymagało wysiłku komunikacyjnego, aby użytkownicy Twojego kodu wiedzieli, że istnieją metody rozszerzenia i gdzie je znaleźć.

Alternatywą jest wybór przestrzeni nazw, dzięki której programiści odkryją metody rozszerzenia za pomocą Intellisense. Więc jeśli chcesz rozszerzyć klasę Foo , logiczne jest umieszczenie metod rozszerzenia w tej samej przestrzeni nazw co Foo .

Ważne jest, aby zdawać sobie sprawę, że nic nie stoi na przeszkodzie, abyś używał przestrzeni nazw „kogoś innego” : System.Linq jeśli chcesz rozszerzyć IEnumerable , możesz dodać swoją metodę rozszerzenia w przestrzeni nazw System.Linq .

To nie zawsze jest dobry pomysł. Na przykład, w jednym konkretnym przypadku, możesz chcieć rozszerzyć wspólny typ (na przykład bool IsApproxEqualTo(this double value, double other) ), ale nie „zanieczyszczaj” całego System . W takim przypadku lepiej wybrać lokalną, specyficzną przestrzeń nazw.

Wreszcie, możliwe jest również umieszczenie metod rozszerzenia w żadnej przestrzeni nazw !

Dobre pytanie referencyjne: Jak zarządzasz przestrzeniami nazw swoich metod rozszerzenia?

Możliwość zastosowania

Tworząc metody rozszerzeń, należy zachować ostrożność, aby upewnić się, że są one odpowiednie dla wszystkich możliwych danych wejściowych i nie dotyczą tylko konkretnych sytuacji. Na przykład możliwe jest rozszerzenie klas systemowych, takich jak string , dzięki czemu nowy kod jest dostępny dla dowolnego łańcucha. Jeśli Twój kod musi wykonywać logikę specyficzną dla domeny w formacie łańcucha specyficznym dla domeny, metoda rozszerzenia nie byłaby odpowiednia, ponieważ jego obecność dezorientowałaby osoby dzwoniące pracujące z innymi łańcuchami w systemie.

Poniższa lista zawiera podstawowe funkcje i właściwości metod rozszerzenia

  1. To musi być metoda statyczna.
  2. Musi znajdować się w klasie statycznej.
  3. Używa słowa kluczowego „this” jako pierwszego parametru z typem w .NET i ta metoda zostanie wywołana przez instancję danego typu po stronie klienta.
  4. Pokazuje to również VS intellisense. Kiedy naciskamy kropkę . po wystąpieniu typu pojawia się w VS intellisense.
  5. Metoda rozszerzenia powinna znajdować się w tej samej przestrzeni nazw, w której jest używana, w przeciwnym razie należy zaimportować przestrzeń nazw klasy za pomocą instrukcji using.
  6. Możesz podać dowolną nazwę dla klasy, która ma metodę rozszerzenia, ale klasa powinna być statyczna.
  7. Jeśli chcesz dodać nowe metody do typu i nie masz dla niego kodu źródłowego, rozwiązaniem jest użycie i wdrożenie metod rozszerzenia tego typu.
  8. Jeśli utworzysz metody rozszerzeń, które mają te same metody podpisu, co typ rozszerzony, metody rozszerzenia nigdy nie będą wywoływane.

Metody rozszerzenia - przegląd

Metody rozszerzenia zostały wprowadzone w C # 3.0. Metody rozszerzeń rozszerzają i dodają zachowanie do istniejących typów bez tworzenia nowego typu pochodnego, ponownej kompilacji lub innego modyfikowania oryginalnego typu. Są one szczególnie pomocne, gdy nie można zmodyfikować źródła typu, który chcesz ulepszyć. Metody rozszerzeń mogą być tworzone dla typów systemów, typów zdefiniowanych przez strony trzecie oraz typów, które sam zdefiniowałeś. Metodę rozszerzenia można wywołać tak, jakby była to metoda członkowska oryginalnego typu. Pozwala to na łączenie metod stosowane do implementacji płynnego interfejsu .

Metodę rozszerzenia tworzy się, dodając metodę statyczną do klasy statycznej, która różni się od rozszerzanego typu oryginalnego. Klasa statyczna przechowująca metodę rozszerzenia jest często tworzona wyłącznie w celu przechowywania metod rozszerzenia.

Metody rozszerzeń przyjmują specjalny pierwszy parametr, który określa rozszerzony typ oryginału. Ten pierwszy parametr jest ozdobiony słowem kluczowym this (co stanowi szczególne i wyraźne użycie this w języku C # - należy to rozumieć jako odmienne od użycia this które pozwala odwoływać się do członków bieżącej instancji obiektu).

W poniższym przykładzie rozszerzanym typem oryginalnym jest string klasy. String został rozszerzony o metodę Shorten() , która zapewnia dodatkową funkcjonalność skracania. Utworzono klasę statyczną StringExtensions do przechowywania metody rozszerzenia. Metoda rozszerzenia Shorten() pokazuje, że jest to przedłużenie string poprzez specjalnie oznaczony pierwszy parametr. Aby pokazać, że metoda Shorten() wydłuża string , pierwszy parametr jest oznaczony this . Dlatego pełną sygnaturą pierwszego parametru jest this string text , gdzie string jest rozszerzanym typem oryginalnym, a text jest nazwą wybranego parametru.

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

Wersja demonstracyjna na żywo .NET Fiddle


Obiekt przekazany jako pierwszy argument metody rozszerzenia (któremu towarzyszy this słowo kluczowe) to instancja, w której wywoływana jest metoda rozszerzenia.

Na przykład po uruchomieniu tego kodu:

"some string".Shorten(5);

Wartości argumentów są następujące:

text: "some string"
length: 5

Zauważ, że metody rozszerzeń są użyteczne tylko wtedy, gdy znajdują się w tym samym obszarze nazw co ich definicja, jeśli obszar nazw jest importowany jawnie przez kod przy użyciu metody rozszerzenia lub jeśli klasa rozszerzeń nie zawiera przestrzeni nazw. Wytyczne .NET Framework zalecają umieszczanie klas rozszerzeń we własnej przestrzeni nazw. Może to jednak prowadzić do problemów z wykrywaniem.

Powoduje to brak konfliktów między metodami rozszerzenia a używanymi bibliotekami, chyba że jawnie wciągnięte zostaną przestrzenie nazw, które mogą powodować konflikty. Na przykład Rozszerzenia LINQ :

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

Wersja demonstracyjna na żywo .NET Fiddle


Od wersji C # 6.0 możliwe jest także umieszczenie dyrektywy using static klasy zawierającej metody rozszerzenia. Na przykład using static System.Linq.Enumerable; . Dzięki temu metody rozszerzeń z tej konkretnej klasy są dostępne bez włączania innych typów z tej samej przestrzeni nazw w zakres.


Gdy dostępna jest metoda klasy o tej samej sygnaturze, kompilator nadaje jej priorytet przed wywołaniem metody rozszerzenia. Na przykład:

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

Demo na żywo w .NET Fiddle


Zauważ, że jeśli istnieją dwie funkcje rozszerzenia o tej samej sygnaturze, a jedna z nich znajduje się w tej samej przestrzeni nazw, wówczas ta pierwsza będzie miała priorytet. Z drugiej strony, jeśli dostęp do obu jest możliwy przy using , to pojawi się błąd czasu kompilacji z komunikatem:

Wywołanie jest niejednoznaczne między następującymi metodami lub właściwościami


Zauważ, że wygoda syntaktyczna wywołania metody rozszerzenia za pomocą originalTypeInstance.ExtensionMethod() jest opcjonalną wygodą. Metodę można również wywołać w tradycyjny sposób, tak aby specjalny parametr pierwszy został użyty jako parametr metody.

Tzn. Obie następujące prace:

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

Jawnie przy użyciu metody rozszerzenia

Metody rozszerzenia mogą być również używane jak zwykłe metody klasy statycznej. Ten sposób wywoływania metody rozszerzenia jest bardziej szczegółowy, ale w niektórych przypadkach jest konieczny.

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

Stosowanie:

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

Kiedy wywoływać metody rozszerzeń jako metody statyczne

Nadal istnieją scenariusze, w których należy użyć metody rozszerzenia jako metody statycznej:

  • Rozwiązywanie konfliktu za pomocą metody członka. Może się to zdarzyć, jeśli nowa wersja biblioteki wprowadza nową metodę członka z tym samym podpisem. W takim przypadku kompilator będzie preferował metodę składową.
  • Rozwiązywanie konfliktów z inną metodą rozszerzenia z tym samym podpisem. Może się to zdarzyć, jeśli dwie biblioteki zawierają podobne metody rozszerzeń, a przestrzenie nazw obu klas z metodami rozszerzenia są używane w tym samym pliku.
  • Przekazywanie metody rozszerzenia jako grupy metod do parametru delegowanego.
  • Robienie własnego wiązania poprzez Reflection .
  • Korzystanie z metody rozszerzenia w oknie Natychmiastowe w Visual Studio.

Używanie statycznego

Jeśli do wprowadzenia globalnego elementu statycznego do klasy globalnej używana jest dyrektywa using static , metody rozszerzenia są pomijane. Przykład:

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

Jeśli usuniesz this modyfikator z pierwszego argumentu metody Shorten , skompiluje się ostatni wiersz.

Sprawdzanie zerowe

Metody rozszerzeń są metodami statycznymi, które zachowują się jak metody instancji. Jednak w przeciwieństwie do tego, co dzieje się podczas wywoływania metody instancji na odwołaniu null , gdy metoda rozszerzenia jest wywoływana z odwołaniem null , nie NullReferenceException . Może to być bardzo przydatne w niektórych scenariuszach.

Rozważmy na przykład następującą klasę statyczną:

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

Wersja demonstracyjna na żywo .NET Fiddle

Metody rozszerzeń mogą wyświetlać tylko publicznych (lub wewnętrznych) członków klasy rozszerzonej

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

Metody rozszerzeń to tylko cukier składniowy i w rzeczywistości nie są członkami klasy, którą rozszerzają. Oznacza to, że nie mogą przerwać enkapsulacji - mają dostęp tylko do public (lub po zaimplementowaniu w tym samym asemblerze, internal ) polach, właściwościach i metodach.

Ogólne metody rozszerzenia

Podobnie jak inne metody, metody rozszerzeń mogą używać ogólnych. Na przykład:

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

i nazywając to byłoby:

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

Zobacz demo

Podobnie w przypadku wielu argumentów typu:

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

Nazywanie to byłoby jak:

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

Zobacz demo

Możesz także utworzyć metody rozszerzeń dla częściowo powiązanych typów w typach wielu ogólnych:

class MyType<T1, T2>
{
}

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

Nazywanie to byłoby jak:

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

Zobacz demo

Możesz także określić ograniczenia typu, where :

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

Kod telefoniczny:

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

Zobacz demo

Wysyłka metod rozszerzeń na podstawie typu statycznego

Do dopasowania parametrów używany jest typ statyczny (czas kompilacji), a nie dynamiczny (typ czasu wykonywania).

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

Wersja demonstracyjna na żywo .NET Fiddle

Również wysyłanie oparte na typie statycznym nie pozwala na wywołanie metody rozszerzenia dla obiektu dynamic :

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

Metody rozszerzenia nie są obsługiwane przez kod dynamiczny.

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

Powodem [wywołanie metod rozszerzenia z kodu dynamicznego] nie jest to, że w zwykłych, niedynamicznych metodach rozszerzenia kodu działa przez pełne przeszukanie wszystkich klas znanych kompilatorowi pod kątem klasy statycznej, która ma metodę rozszerzenia pasującą . Wyszukiwanie odbywa się w kolejności zgodnej z zagnieżdżaniem przestrzeni nazw i dostępne using dyrektyw w każdej przestrzeni nazw.

Oznacza to, że aby poprawnie wywołać metodę dynamicznego rozszerzenia, DLR musi wiedzieć w czasie wykonywania, jakie wszystkie zagnieżdżenia przestrzeni nazw i using dyrektyw znajdują się w kodzie źródłowym . Nie mamy przydatnego mechanizmu do kodowania wszystkich tych informacji w witrynie wywołującej. Zastanawialiśmy się nad wynalezieniem takiego mechanizmu, ale zdecydowaliśmy, że jest on zbyt kosztowny i powoduje zbyt duże ryzyko harmonogramu, aby było tego warte.

Źródło

Metody rozszerzeń jako silnie typowane opakowania

Metod rozszerzeń można używać do pisania silnie typowanych opakowań dla obiektów podobnych do słowników. Na przykład pamięć podręczna 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;
}

Takie podejście eliminuje potrzebę używania literałów łańcuchowych jako kluczy w całej bazie kodu, a także potrzebę rzutowania na wymagany typ podczas operacji odczytu. Ogólnie tworzy bardziej bezpieczny, silnie typowany sposób interakcji z tak luźnymi typami obiektów, jak Słowniki.

Metody przedłużania łańcuchów

Gdy metoda rozszerzenia zwraca wartość tego samego typu co this argument, można jej użyć do „powiązania” jednego lub więcej wywołań metody z kompatybilnym podpisem. Może to być przydatne w przypadku typów zapieczętowanych i / lub pierwotnych oraz umożliwia tworzenie tzw. „Płynnych” interfejsów API, jeśli nazwy metod brzmią jak naturalny ludzki język.

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

Lub tak

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

Metody rozszerzenia w połączeniu z interfejsami

Bardzo wygodne jest stosowanie metod rozszerzeń z interfejsami, ponieważ implementacja może być przechowywana poza klasą, a do dodania funkcji do klasy wystarczy jedynie udekorować klasę interfejsem.

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

użyj jak:

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

IList Przykład metody rozszerzenia: Porównanie 2 list

Możesz użyć następującej metody rozszerzenia do porównania zawartości dwóch instancji IList <T> tego samego typu.

Domyślnie elementy są porównywane na podstawie ich kolejności na liście, a same elementy, przekazanie wartości false do parametru isOrdered spowoduje porównanie tylko samych elementów bez względu na ich kolejność.

Aby ta metoda działała, typ ogólny ( T ) musi zastąpić zarówno metody Equals jak i GetHashCode .

Stosowanie:

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

Metoda:

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

Metody rozszerzeń z wyliczeniem

Metody rozszerzeń są przydatne do dodawania funkcji do wyliczeń.

Jednym z powszechnych zastosowań jest implementacja metody konwersji.

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

Teraz możesz szybko przekonwertować wartość wyliczenia na inny typ. W tym przypadku bool.

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

Alternatywnie można zastosować metody rozszerzenia, aby dodać metody podobne do właściwości.

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

Rozszerzenia i interfejsy razem umożliwiają kod DRY i funkcjonalność podobną do miksu

Metody rozszerzeń pozwalają uprościć definicje interfejsów, włączając tylko podstawową wymaganą funkcjonalność w samym interfejsie i umożliwiając zdefiniowanie metod wygody i przeciążeń jako metod rozszerzeń. Interfejsy z mniejszą liczbą metod są łatwiejsze do wdrożenia w nowych klasach. Utrzymywanie przeciążeń jako rozszerzeń zamiast włączania ich do interfejsu bezpośrednio oszczędza Ci kopiowania kodu płyty głównej do każdej implementacji, pomagając zachować Twój kod w stanie SUCHYM. To w rzeczywistości jest podobne do wzorca mieszania, którego C # nie obsługuje.

System.Linq.Enumerable do IEnumerable<T> są tego doskonałym przykładem. IEnumerable<T> wymaga tylko, aby klasa implementująca zaimplementowała dwie metody: ogólną i nie-ogólną GetEnumerator() . Ale System.Linq.Enumerable zapewnia niezliczone użyteczne narzędzia jako rozszerzenia umożliwiające zwięzłe i jasne korzystanie z IEnumerable<T> .

Poniżej przedstawiono bardzo prosty interfejs z przeciążeniami wygody dostarczanymi jako rozszerzenia.

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

Metody rozszerzenia do obsługi przypadków specjalnych

Metody rozszerzenia mogą być używane do „ukrywania” przetwarzania nieeleganckich reguł biznesowych, które w innym przypadku wymagałyby zaśmiecania funkcji wywołującej za pomocą instrukcji if / then. Jest to podobne i analogiczne do obsługi wartości NULL metodami rozszerzenia. Na przykład,

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.

Korzystanie z metod rozszerzenia z metodami statycznymi i wywołaniami zwrotnymi

Rozważ użycie metod rozszerzeń jako funkcji, które zawijają inny kod, oto świetny przykład, który używa zarówno metody statycznej, jak i metody rozszerzenia do zawijania konstrukcji Try Catch. Dokonaj kodu 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
        }
    }
}

Metody rozszerzenia interfejsów

Jedną przydatną funkcją metod rozszerzeń jest to, że można tworzyć wspólne metody interfejsu. Zwykle interfejs nie może mieć wspólnych implementacji, ale mogą to robić metody rozszerzenia.

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

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

W tym przykładzie metodę FeetDriven można zastosować w dowolnym IVehicle . Ta logika w tej metodzie miałaby zastosowanie do wszystkich IVehicle , więc można to zrobić w ten sposób, aby nie było potrzeby korzystania z FeetDriven w definicji IVehicle która byłaby wdrożona w taki sam sposób dla wszystkich dzieci.

Używanie metod Extension do tworzenia pięknych klas maperów

Możemy stworzyć lepsze klasy mapujące za pomocą metod rozszerzenia. Załóżmy, że mam takie klasy DTO jak

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

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

i muszę zmapować do odpowiednich klas modeli widoków

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

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

wtedy mogę utworzyć moją klasę mapowania jak poniżej

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

W końcu mogę wywołać mojego program do mapowania, jak poniżej

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

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

Piękno tutaj polega na tym, że wszystkie metody mapowania mają wspólną nazwę (ToViewModel) i możemy użyć go na kilka sposobów

Korzystanie z metod rozszerzenia do tworzenia nowych typów kolekcji (np. DictList)

Można tworzyć metody rozszerzeń, aby poprawić użyteczność zagnieżdżonych kolekcji, takich jak Dictionary z wartością List<T> .

Rozważ następujące metody rozszerzenia:

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

możesz użyć metod rozszerzenia w następujący sposób:

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

Zobacz demo



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow