-
- I delegati sono oggetti immutabili, quindi, ad esempio ogni chiamata di sottoscrizione o cancellazione +=/-= determina sempre la creazione di una nuova istanza.
- Tutti i delegati sono implicitamente di tipo System.MultiCastDelegate che a sua volta estende System.Delegate
- Quando viene assegnato un metodo di classe, l'istanza della classe viene referenziata dal delegato. Conoscere questo è molto importante perché possibile causa di memory leak non previsti: In caso di eventi statici sottoscritti da un metodo di classe, allora l'istanza di quest'ultima non viene rilasciata se non viene effettuata la rimozione della sottoscrizione (Vedi ad es. Windows.Forms, ToolStrip and Memory Leak
- I delegati supportano la varianza e la covarianza secondo logiche simili a quelle delle interfacce (vedi Re[studing] C# 5 - (... covariance and controvariance))
- Questo spiega l'utilizzo dello standard degli eventi basato sulla classe System.EventArgs che essendo un parametro di input supporta la controvarianza
- Seguendo la stessa regola delle interfacce generic, per sfruttare la varianza e controvarianza con i generic delegates dobbiamo esplicitare la direzione dei parametri. (I vari generic delegates Action e Function sono definiti seguendo questa logica
- delegate TResult Func<out TResult>(); - Varianza
- delegate void Action<in T>(T arg); - Controvarianza
-
- Il brodcaster è la classe che invoca l'evento, il subscriber il metodo che sottoscrive l'evento.
- Aggiungendo la keyword event nell'esposizione del metodo:
- Il compilatore incapsula il delegato, in particolare espone solo Add (+=) e Remove (-=)
- Il compilatore implementa un meccanismo di sincronizzazione (lock-free compare-and-swap alghoritm)
- Per evitare side-effects in ambito multi-thread copiare all'interno del metodo di invocazione il delegato prima di effetuare l'invoke
- I delegati EventHandler ed EventHandler<T> implementano il pattern .NET standard degli eventi
Code Snippet
- class Broadcaster
- {
- public event EventHandler<EventArgs> EventOccured;
-
- public void InvokeOnEventOccurred()
- {
- OnEventOccured();
- }
-
- public Task InvokeOnEventOccurredAsync()
- {
- return Task.Factory.StartNew(OnEventOccured);
- }
-
- protected virtual void OnEventOccured()
- {
- var eventOccured = EventOccured;
- if (eventOccured != null)
- EventOccured(this, EventArgs.Empty);
- }
- }
-
- Una lambda expression è una funzione o un metodo anonimo, nella forma (parameters) => expression-or-statment-block, quest'ultima può definire:
- Un istanza di un delegato: System.Delegate
- Un albero di espressione: System.Linq.Expressions.Expression<TDelegate>
- Nel caso più comune, il delegato, il compilatore genera un metodo privato nel quale sposta l'implementazione
- I parametri di una Lambda Expressions possono essere "puri" oppure specificati es: (int x) => x * x
- Quando una variabile esterna è utilizzata all'interno di una lambda expression, quest'ultima viene chiamata captured variable, mentre l'espressione viene chiamata closure, le importanti conseguenze sono:
- Il ciclo di vita delle variabili catturato è esteso a quello del delegato
- Le variabili sono valutate quando il delegato è invocato e non quando la variabile è catturata. (NOTA: Questo vale anche per gli Anonymous Methods in gran parte assorbiti dalle Lambda Expressions)
Code Snippet
- [Test]
- public void TestOnCapturedVariable()
- {
- var exprs = new Func<int>[3];
-
- for (int i = 0; i < 3; i++)
- exprs[i] = () => i;
-
- Assert.AreEqual(3, exprs[0]());
- Assert.AreEqual(3, exprs[1]());
- Assert.AreEqual(3, exprs[2]());
- }
-
- Le eccezioni sono molto pesanti da gestire occupando centinaia di cicli di clock.
- Le eccezioni .NET devono essere sottoclassi di System.Exception (Questo non è vero in C++ dove è possibile "lanciare"
qualsiasi oggetto come eccezione, in questo ultimo caso il CLR incapsula l'oggetto in una
System.Runtime.CompilerServices.RuntimeWrappedException)
- Importante: Quando si gestisce cattura un eccezione con lo statment catch è possibile "rilanciarla" con lo statment throw, con
la sottile differenza che throw; (senza parametri) rilancia l'eccezione originale senza modificarne gli attributi mentre throw ex; (dove ex è il nome della
variabile assegnato all'eccezione nello statment catch) modifica l'eccezione propagata cambiando lo StackTrace che non riflette
più quello dell'eccezione originale.
Code Snippet
- [Test]
- public void TestDifferentStackTraceOnExceptionThrown()
- {
- try
- {
- CaptureExceptionAndRethrowWithoutParameter();
- }
- catch (Exception ex)
- {
- Console.WriteLine("-----CaptureExceptionAndRethrowWithoutParameter-----");
- Console.WriteLine(ex.StackTrace);
- Console.WriteLine("----------------------------------------------------");
- Console.WriteLine();
- }
-
- try
- {
- CaptureExceptionAndRethrowWithParameter();
- }
- catch (Exception ex)
- {
- Console.WriteLine("-----CaptureExceptionAndRethrowWithParameter-----");
- Console.WriteLine(ex.StackTrace);
- Console.WriteLine("-------------------------------------------------");
- Console.WriteLine();
- }
- }
-
- void CaptureExceptionAndRethrowWithoutParameter()
- {
- try
- {
- NotImplementedMethod();
- }
- catch (Exception ex)
- {
- throw;
- }
- }
-
- void CaptureExceptionAndRethrowWithParameter()
- {
- try
- {
- NotImplementedMethod();
- }
- catch (Exception ex)
- {
- throw ex;
- }
- }
-
- void NotImplementedMethod()
- {
- throw new NotImplementedException();
- }
Di seguito lo Stack Trace notate le differenze, nel secondo cas perdiamo l'origine dell'eccezione
-----CaptureExceptionAndRethrowWithoutParameter-----
in snftest.CSharp5.AdvancedCSharp.NotImplementedMethod() in c:\Sviluppo\privatedev.sinformatic\Libreria\snftest\CSharp5\AdvancedCSharp.cs:riga 100
in snftest.CSharp5.AdvancedCSharp.CaptureExceptionAndRethrowWithoutParameter() in c:\Sviluppo\privatedev.sinformatic\Libreria\snftest\CSharp5\AdvancedCSharp.cs:riga 84
in snftest.CSharp5.AdvancedCSharp.TestDifferentStackTraceOnExceptionThrown() in c:\Sviluppo\privatedev.sinformatic\Libreria\snftest\CSharp5\AdvancedCSharp.cs:riga 51
----------------------------------------------------
-----CaptureExceptionAndRethrowWithParameter-----
in snftest.CSharp5.AdvancedCSharp.CaptureExceptionAndRethrowWithParameter() in c:\Sviluppo\privatedev.sinformatic\Libreria\snftest\CSharp5\AdvancedCSharp.cs:riga 96
in snftest.CSharp5.AdvancedCSharp.TestDifferentStackTraceOnExceptionThrown() in c:\Sviluppo\privatedev.sinformatic\Libreria\snftest\CSharp5\AdvancedCSharp.cs:riga 63
-------------------------------------------------
-
- Un enumeratore si può definire come un cursore in sola lettura, che
può iterare solo in avanti, su di una sequenza di elementi.
- NOTA: Qualsiasi oggetto implementi MoveNext() and Current è enumerabile.
- Quando un oggetto enumeratore implementa IDisposable, lo statment foreach() chiama Dispose() al termine dell'enumerazione.
- Un oggetto enumerabile espone una sequenza logica di elementi, la differenza sostanziale è che l'oggetto enumerabile NON è un cursore ma PRODUCE un cursore.
- Lo statment foreach implementa l'iterazione su un oggetto enumerabile chiedendo all'oggetto di produrre l'enumeratore con GetEnumerator() per iterarlo.
- yield keyword
- C'è una sostanziale differenza tra un restituire l'istanza di un oggetto enumeratore o enumerabile oppure usare la parola chiave yield.
- Quando si usa uno statment yield return x (dove x è il tipo da enumerare) si esprime il concetto di ritornare il "prossimo elemento richiesto dell'enumerazione", questo richiede che lo stato del chiamante sia conservato fino alla prossima iternazione (MoveNext()).
- Dal punto di vista del programma questo approccio modifica profondamente il flusso di esecuzione: La funzione viene eseguita solo quando è enumerata e non quando viene chiamata, infatti il compilatore incapsula il nostro metodo in un oggetto enumeratore, in grado di conservare lo stato del metodo.
- Lo statment yield break interrompe l'enumerazione, non è obbligatorio a meno che non sia necessario interrompere prematuramente l'iterazione.
- Per ragioni di complessità l'istruzione yield non può essere presente in un blocco try/catch/[finally] mentre può comparire nella clausola try di un blocco try/finally
Code Snippet
- static int GeneratedNumbers = 0;
-
- [Test]
- public void EnumerateFirstHundredIntegerNumbers()
- {
- GeneratedNumbers = 0;
- foreach (var number in IntNumbers)
- if (number == 99)
- break;
- Assert.AreEqual(100,GeneratedNumbers);
- }
-
- IEnumerable<int> IntNumbers
- {
- get
- {
- for (int i = 0; i < int.MaxValue;
i++)
- {
- GeneratedNumbers++;
- yield return i;
- }
- }
- }
-
- Quando dichiaramo un T? dove T è un value-type (tipo nativo o struct) la nostra dichiarazione viene tradotta
dal compilatore come Nullable<T>.
- Il compilatore ridefinisce automaticamente gli operatori, come > < o ==, per renderli "semanticamente uguali"
agli operatori dei corrispondenti not nullable value-type.
- Ad esempio: a < b viene ridefinito come (a.HasValue && b.HasValue) ? (a.Value < b.Value) : false
- Nel caso di confronto con gli operatori logici & oppure | il null viene considerato come valore sconosciuto
- Ad esempio: a | b dove a è true è b nullo il risultato è true, mentre se a fosse false allora il risultato sarebbe null