C# Language
Metodi di estensione
Ricerca…
Sintassi
- public static ReturnType MyExtensionMethod (target TargetType)
- public static ReturnType MyExtensionMethod (target TargetType, TArg1 arg1, ...)
Parametri
Parametro | Dettagli |
---|---|
Questo | Il primo parametro di un metodo di estensione deve essere sempre preceduta dalla this parola chiave, seguita dal identificativo con cui fare riferimento all'istanza "corrente" dell'oggetto che si sta estendendo |
Osservazioni
I metodi di estensione sono zucchero sintattico che consente di invocare metodi statici su istanze di oggetti come se fossero membri del tipo stesso.
I metodi di estensione richiedono un oggetto target esplicito. Sarà necessario utilizzare this
parola chiave per accedere al metodo all'interno del tipo esteso stesso.
I metodi di estensione devono essere dichiarati statici e devono vivere in una classe statica.
Quale spazio dei nomi?
La scelta dello spazio dei nomi per la classe del metodo di estensione è un compromesso tra visibilità e rilevabilità.
L' opzione più comunemente citata è quella di avere uno spazio dei nomi personalizzato per i metodi di estensione. Tuttavia ciò comporterà uno sforzo di comunicazione in modo che gli utenti del tuo codice sappiano che esistono i metodi di estensione e dove trovarli.
Un'alternativa è scegliere uno spazio dei nomi in modo tale che gli sviluppatori scoprano i metodi di estensione tramite Intellisense. Quindi, se si desidera estendere la classe Foo
, è logico inserire i metodi di estensione nello stesso spazio dei nomi di Foo
.
È importante rendersi conto che nulla impedisce di utilizzare lo spazio dei nomi di "qualcun altro" : quindi se si desidera estendere IEnumerable
, è possibile aggiungere il proprio metodo di estensione nello spazio System.Linq
nomi System.Linq
.
Questa non è sempre una buona idea. Ad esempio, in un caso specifico, potresti voler estendere un tipo comune ( bool IsApproxEqualTo(this double value, double other)
per esempio), ma non avere quel 'inquinante' l'intero System
. In questo caso è preferibile scegliere un namespace locale, specifico.
Infine, è anche possibile inserire i metodi di estensione in nessuno spazio dei nomi !
Una buona domanda di riferimento: come gestisci gli spazi dei nomi dei tuoi metodi di estensione?
applicabilità
Bisogna fare attenzione quando si creano metodi di estensione per assicurarsi che siano appropriati per tutti i possibili input e non siano solo rilevanti per situazioni specifiche. Ad esempio, è possibile estendere classi di sistema come la string
, che rende il nuovo codice disponibile per qualsiasi stringa. Se il codice deve eseguire una logica specifica del dominio su un formato stringa specifico del dominio, un metodo di estensione non sarebbe appropriato in quanto la sua presenza confonderebbe i chiamanti che lavorano con altre stringhe nel sistema.
Il seguente elenco contiene le funzionalità e le proprietà di base dei metodi di estensione
- Deve essere un metodo statico.
- Deve essere posizionato in una classe statica.
- Utilizza la parola chiave "this" come primo parametro con un tipo in .NET e questo metodo verrà chiamato da una determinata istanza di tipo sul lato client.
- E 'anche mostrato da VS intellisense. Quando premiamo il punto
.
dopo un'istanza di tipo, allora è disponibile in VS intellisense. - Un metodo di estensione deve essere nello stesso spazio dei nomi utilizzato o è necessario importare lo spazio dei nomi della classe mediante un'istruzione using.
- Puoi dare qualsiasi nome per la classe che ha un metodo di estensione ma la classe dovrebbe essere statica.
- Se si desidera aggiungere nuovi metodi a un tipo e non si dispone del codice sorgente, la soluzione consiste nell'utilizzare e implementare i metodi di estensione di quel tipo.
- Se si creano metodi di estensione con gli stessi metodi di firma del tipo che si sta estendendo, i metodi di estensione non verranno mai chiamati.
Metodi di estensione: panoramica
I metodi di estensione sono stati introdotti in C # 3.0. I metodi di estensione estendono e aggiungono comportamenti a tipi esistenti senza creare un nuovo tipo derivato, ricompilando o modificando in altro modo il tipo originale. Sono particolarmente utili quando non è possibile modificare la fonte di un tipo che si desidera migliorare. I metodi di estensione possono essere creati per tipi di sistema, tipi definiti da terze parti e tipi definiti dall'utente. Il metodo di estensione può essere invocato come se fosse un metodo membro del tipo originale. Ciò consente di concatenare il metodo utilizzato per implementare un'interfaccia fluida .
Un metodo di estensione viene creato aggiungendo un metodo statico a una classe statica distinta dal tipo originale che viene esteso. La classe statica che contiene il metodo di estensione è spesso creata al solo scopo di contenere i metodi di estensione.
I metodi di estensione prendono uno speciale primo parametro che designa il tipo originale che viene esteso. Questo primo parametro è decorato con la parola chiave this
(che costituisce un uso speciale e distinto di this
in C # -it dovrebbe essere compreso come differente dall'uso di this
che consente di fare riferimento ai membri dell'istanza dell'oggetto corrente).
Nell'esempio seguente, il tipo originale che è esteso è la string
classe. String
è stata estesa da un metodo Shorten()
, che fornisce le funzionalità aggiuntive di accorciamento. La classe statica StringExtensions
è stata creata per contenere il metodo di estensione. Il metodo di estensione Shorten()
mostra che si tratta di un'estensione della string
tramite il primo parametro appositamente contrassegnato. Per mostrare che il metodo Shorten()
estende la string
, il primo parametro è contrassegnato da this
. Pertanto, la firma completa del primo parametro è this string text
, dove string
è il tipo originale che viene esteso e il text
è il nome del parametro scelto.
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);
}
}
L'oggetto passato come primo argomento di un metodo di estensione (che è accompagnato dalla parola chiave this
) è l'istanza in cui viene chiamato il metodo di estensione.
Ad esempio, quando viene eseguito questo codice:
"some string".Shorten(5);
I valori degli argomenti sono i seguenti:
text: "some string"
length: 5
Si noti che i metodi di estensione sono utilizzabili solo se si trovano nello stesso spazio dei nomi della loro definizione, se lo spazio dei nomi viene importato in modo esplicito dal codice utilizzando il metodo di estensione o se la classe di estensione non ha spazio dei nomi. Le linee guida di .NET framework raccomandano di inserire le classi di estensione nel proprio spazio dei nomi. Tuttavia, questo può portare a problemi di scoperta.
Ciò non provoca conflitti tra i metodi di estensione e le librerie utilizzate, a meno che gli spazi dei nomi che potrebbero essere in conflitto siano esplicitamente inseriti. Ad esempio, LINQ Estensioni :
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);
}
}
Dal momento che C # 6.0, è anche possibile inserire una direttiva using static
la classe che contiene i metodi di estensione. Ad esempio, using static System.Linq.Enumerable;
. Ciò rende i metodi di estensione da quella particolare classe disponibile senza portare altri tipi dallo stesso spazio dei nomi in ambito.
Quando un metodo di classe con la stessa firma è disponibile, il compilatore dà la priorità alla chiamata del metodo di estensione. Per esempio:
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"
}
}
Notare che se ci sono due funzioni di estensione con la stessa firma e una di esse si trova nello stesso spazio dei nomi, a quella verrà assegnata una priorità. D'altra parte, se si accede a entrambi using
, allora si verificherà un errore di compilazione con il messaggio:
La chiamata è ambigua tra i seguenti metodi o proprietà
Si noti che la convenienza sintattica di chiamare un metodo di estensione tramite originalTypeInstance.ExtensionMethod()
è una comodità opzionale. Il metodo può anche essere chiamato nel modo tradizionale, in modo che il primo parametro speciale venga utilizzato come parametro per il metodo.
Vale a dire, entrambi i seguenti lavori:
//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);
Utilizzo esplicito di un metodo di estensione
I metodi di estensione possono anche essere usati come normali metodi di classi statiche. Questo modo di chiamare un metodo di estensione è più dettagliato, ma è necessario in alcuni casi.
static class StringExtensions
{
public static string Shorten(this string text, int length)
{
return text.Substring(0, length);
}
}
Uso:
var newString = StringExtensions.Shorten("Hello World", 5);
Quando chiamare i metodi di estensione come metodi statici
Esistono ancora scenari in cui è necessario utilizzare un metodo di estensione come metodo statico:
- Risolvere il conflitto con un metodo membro. Questo può accadere se una nuova versione di una libreria introduce un nuovo metodo membro con la stessa firma. In questo caso, il metodo membro sarà preferito dal compilatore.
- Risolvere i conflitti con un altro metodo di estensione con la stessa firma. Ciò può accadere se due librerie includono metodi di estensione e spazi dei nomi simili di entrambe le classi con metodi di estensione utilizzati nello stesso file.
- Passando il metodo di estensione come un gruppo di metodi in un parametro delegato.
- Esegui il tuo legame con
Reflection
. - Utilizzo del metodo di estensione nella finestra Immediata in Visual Studio.
Uso statico
Se si using static
direttiva using static
per portare i membri statici di una classe statica in ambito globale, i metodi di estensione vengono saltati. Esempio:
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);
Se rimuovi this
modificatore dal primo argomento del metodo Shorten
, l'ultima riga verrà compilata.
Controllo nullo
I metodi di estensione sono metodi statici che si comportano come metodi di istanza. Tuttavia, a differenza di ciò che accade quando si chiama un metodo di istanza su un riferimento null
, quando un metodo di estensione viene chiamato con un riferimento null
, non lancia una NullReferenceException
. Questo può essere abbastanza utile in alcuni scenari.
Ad esempio, si consideri la seguente classe statica:
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
I metodi di estensione possono vedere solo i membri pubblici (o interni) della classe estesa
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
}
}
I metodi di estensione sono solo uno zucchero sintattico e non sono in realtà membri della classe che estendono. Ciò significa che non possono interrompere l'incapsulamento: hanno solo accesso al public
(o quando è implementato nei campi, proprietà e metodi dello stesso assembly, internal
).
Metodi di estensione generici
Proprio come altri metodi, i metodi di estensione possono utilizzare i generici. Per esempio:
static class Extensions
{
public static bool HasMoreThanThreeElements<T>(this IEnumerable<T> enumerable)
{
return enumerable.Take(4).Count() > 3;
}
}
e chiamandolo sarebbe come:
IEnumerable<int> numbers = new List<int> {1,2,3,4,5,6};
var hasMoreThanThreeElements = numbers.HasMoreThanThreeElements();
Allo stesso modo per più argomenti di tipo:
public static TU GenericExt<T, TU>(this T obj)
{
TU ret = default(TU);
// do some stuff with obj
return ret;
}
Chiamarlo sarebbe come:
IEnumerable<int> numbers = new List<int> {1,2,3,4,5,6};
var result = numbers.GenericExt<IEnumerable<int>,String>();
È anche possibile creare metodi di estensione per tipi parzialmente vincolati in tipi multi generici:
class MyType<T1, T2>
{
}
static class Extensions
{
public static void Example<T>(this MyType<int, T> test)
{
}
}
Chiamarlo sarebbe come:
MyType<int, string> t = new MyType<int, string>();
t.Example();
Puoi anche specificare i vincoli di tipo con where
:
public static bool IsDefault<T>(this T obj) where T : struct, IEquatable<T>
{
return EqualityComparer<T>.Default.Equals(obj, default(T));
}
Chiamare il codice:
int number = 5;
var IsDefault = number.IsDefault();
Invio di metodi di estensione in base al tipo statico
Viene utilizzato il tipo statico (in fase di compilazione) anziché il dinamico (tipo runtime) per abbinare i parametri.
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"
}
}
Inoltre, il dispatch basato sul tipo statico non consente di chiamare un metodo di estensione su un oggetto 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
I metodi di estensione non sono supportati dal codice dinamico.
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");
}
}
La ragione per cui [chiamare i metodi di estensione dal codice dinamico] non funziona è perché nei normali metodi di estensione del codice non dinamico funziona eseguendo una ricerca completa di tutte le classi note al compilatore per una classe statica che ha un metodo di estensione che corrisponde . La ricerca va in ordine in base alla nidificazione dello spazio dei nomi e disponibile
using
direttive in ogni spazio dei nomi.Ciò significa che per ottenere una chiamata al metodo di estensione dinamica risolta correttamente, in qualche modo il DLR deve sapere in fase di esecuzione quali sono stati tutti i nidi di spazio dei nomi e le direttive di
using
nel codice sorgente . Non abbiamo un meccanismo a portata di mano per codificare tutte queste informazioni nel sito di chiamata. Abbiamo preso in considerazione l'idea di inventare un meccanismo del genere, ma abbiamo deciso che era troppo costoso e ha prodotto un rischio di pianificazione eccessivo per meritarlo.
Metodi di estensione come wrapper fortemente tipizzati
I metodi di estensione possono essere utilizzati per scrivere wrapper fortemente tipizzati per oggetti simili a dizionari. Ad esempio una cache, HttpContext.Items
in 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;
}
Questo approccio elimina la necessità di utilizzare stringhe letterali come chiavi in tutto il codebase così come la necessità di eseguire il casting sul tipo richiesto durante l'operazione di lettura. Complessivamente, crea un modo più sicuro e fortemente tipizzato di interagire con tali oggetti vagamente tipizzati come dizionari.
Metodi di estensione per concatenare
Quando un metodo di estensione restituisce un valore che ha lo stesso tipo di this
argomento, può essere utilizzato per "concatenare" una o più chiamate di metodo con una firma compatibile. Ciò può essere utile per i tipi sealed e / o primitivi e consente la creazione di cosiddette API "fluide" se i nomi dei metodi vengono letti come il linguaggio naturale umano.
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;
}
}
O come questo
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();
}
}
Metodi di estensione in combinazione con interfacce
È molto conveniente usare i metodi di estensione con le interfacce poiché l'implementazione può essere archiviata al di fuori della classe e tutto ciò che serve per aggiungere alcune funzionalità alla classe è di decorare la classe con l'interfaccia.
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();
}
}
usare come:
var classy = new Classy();
classy.Do(); // will call the extension
classy.DoWith(); // Classy implements IInterface so it can also be called this way
IList Esempio di metodo di estensione: confronto di 2 elenchi
È possibile utilizzare il seguente metodo di estensione per confrontare i contenuti di due istanze IList <T> dello stesso tipo.
Per impostazione predefinita, gli elementi vengono confrontati in base al loro ordine all'interno dell'elenco e gli elementi stessi, passando false al parametro isOrdered
confronteranno solo gli elementi stessi indipendentemente dal loro ordine.
Affinché questo metodo funzioni, il tipo generico ( T
) deve sostituire i metodi Equals
e GetHashCode
.
Uso:
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
Metodo:
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;
}
}
Metodi di estensione con enumerazione
I metodi di estensione sono utili per aggiungere funzionalità alle enumerazioni.
Un uso comune è implementare un metodo di conversione.
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;
}
}
Ora puoi convertire rapidamente il tuo valore enum in un tipo diverso. In questo caso un bool.
bool yesNoBool = YesNo.Yes.ToBool(); // yesNoBool == true
YesNo yesNoEnum = false.ToYesNo(); // yesNoEnum == YesNo.No
In alternativa, i metodi di estensione possono essere utilizzati per aggiungere metodi come proprietà.
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();
Le estensioni e le interfacce insieme abilitano il codice DRY e le funzionalità di mixin-like
I metodi di estensione consentono di semplificare le definizioni dell'interfaccia includendo solo le funzionalità essenziali richieste nell'interfaccia stessa e consentendo di definire metodi di convenienza e sovraccarichi come metodi di estensione. Le interfacce con meno metodi sono più facili da implementare nelle nuove classi. Mantenere sovraccarichi come estensioni piuttosto che includerli nell'interfaccia consente di risparmiare direttamente dal codice copiato in ogni implementazione, aiutandoti a mantenere il tuo codice ASCIUTTO. Questo in effetti è simile al pattern di mixin che C # non supporta.
System.Linq.Enumerable
estensioni di System.Linq.Enumerable
a IEnumerable<T>
sono un ottimo esempio di ciò. IEnumerable<T>
richiede solo che la classe di implementazione implementa due metodi: GetEnumerator()
generico e non generico GetEnumerator()
. Ma System.Linq.Enumerable
fornisce innumerevoli utilità utili come estensioni che consentono un consumo conciso e chiaro di IEnumerable<T>
.
Quanto segue è un'interfaccia molto semplice con sovraccarichi di comodità forniti come estensioni.
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)}");
}
}
Metodi di estensione per la gestione di casi speciali
I metodi di estensione possono essere utilizzati per "nascondere" l'elaborazione di regole aziendali poco eleganti che altrimenti richiederebbero una funzione di chiamata con istruzioni if / then. Questo è simile e analogo alla gestione dei valori null con i metodi di estensione. Per esempio,
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.
Utilizzo dei metodi di estensione con metodi statici e callback
Considera l'utilizzo di metodi di estensione come funzioni che racchiudono un altro codice, ecco un ottimo esempio che utilizza sia un metodo statico che un metodo di estensione per racchiudere il costrutto Try Catch. Crea il tuo codice 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
}
}
}
Metodi di estensione su interfacce
Una funzione utile dei metodi di estensione è che è possibile creare metodi comuni per un'interfaccia. Normalmente un'interfaccia non può avere implementazioni condivise, ma con i metodi di estensione che possono.
public interface IVehicle
{
int MilesDriven { get; set; }
}
public static class Extensions
{
public static int FeetDriven(this IVehicle vehicle)
{
return vehicle.MilesDriven * 5028;
}
}
In questo esempio, il metodo FeetDriven
può essere utilizzato su qualsiasi IVehicle
. Questa logica in questo metodo si applicherebbe a tutti gli IVehicle
, quindi può essere fatta in questo modo in modo che non ci debba essere un FeetDriven
nella definizione IVehicle
che sarebbe implementata allo stesso modo per tutti i bambini.
Utilizzo dei metodi di estensione per creare bellissime classi di mapping
Possiamo creare classi di mapper migliori con i metodi di estensione, Supponiamo di avere alcune classi DTO
public class UserDTO
{
public AddressDTO Address { get; set; }
}
public class AddressDTO
{
public string Name { get; set; }
}
e ho bisogno di mappare a classi di modelli di vista corrispondenti
public class UserViewModel
{
public AddressViewModel Address { get; set; }
}
public class AddressViewModel
{
public string Name { get; set; }
}
quindi posso creare la mia classe di mapper come di seguito
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
};
}
}
Quindi finalmente posso richiamare il mio mapper come di seguito
UserDTO userDTOObj = new UserDTO() {
Address = new AddressDTO() {
Name = "Address of the user"
}
};
UserViewModel user = userDTOObj.ToViewModel(); // My DTO mapped to Viewmodel
Il bello qui è che tutti i metodi di mappatura hanno un nome comune (ToViewModel) e possiamo riutilizzarlo in diversi modi
Utilizzo dei metodi di estensione per creare nuovi tipi di raccolta (ad esempio DictList)
È possibile creare metodi di estensione per migliorare l'usabilità per le raccolte nidificate come un Dictionary
con un valore di List<T>
.
Considera i seguenti metodi di estensione:
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;
}
}
puoi usare i metodi di estensione come segue:
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