Continuos integration di un proggetto OpenSource su github con Travis CI


Ho conosciuto recentemente Travis CI un servizio di continuous integration integrato con github. Così ho deciso di sperimentarlo con il mio primo progetto open source...

Integrazione con Travis CI

Risultato

Adesso ogni commit al sorgente del mio repository scatena una corrispondente build e l'esecuzione dei test automatici.

I risultati dell'ultima build (del branch master) sono visibili graficamente dalla pagina principale del mio progetto github...

FxCommon - Readme - First Travis CI integration

author: Marco Baldessari | posted @ Saturday, March 21, 2015 3:44 PM | Feedback (0)

API Ergonomics


Diversi anni fa (fine 2008) pubblicai un post intitolato "ergonomia dell'architettura", oggi mi sento di fare un punto pensando anche all'evoluzione della programmazione in generale. Ovviamente tutto con spirito IMHO!

Una nuova definizione

Dopo attenta riflessione, ho ribattezato il vecchio post in API ergonomics (mi piace tanto come suona in inglese :)), che tradotto in italiano è ergonomia dell'interfaccia di programmazione di un'applicazione. La definizione contenuta nelle prime righe di wikipedia di Application programming interface è calzante e musa ispiratrice per aprire questo post: "Con Application Programming Interface (in acronimo API, in italiano Interfaccia di Programmazione di un'Applicazione), in informatica, si indica ogni insieme di procedure disponibili al programmatore, di solito raggruppate a formare un set di strumenti specifici per l'espletamento di un determinato compito all'interno di un certo programma. Spesso con tale termine si intendono le librerie software disponibili in un certo linguaggio di programmazione" (Ho evidenziato punti chiave in grassetto)

Le API independemente dalla modalità di pubblicazione, dal linguaggio in cui sono state realizzate o dal sistema operativo/architettura fisica in cui si eseguono, sono strumenti disponibili al programmatore per realizzare il proprio lavoro, cioè il software.

Ergonomia nell'industria

Quando ero ancora un ragazzo, oltre alla mia prima passione, l'informatica, mi piaceva l'elettronica, una volta rimasi affascinato da una semplice riflessione sulla maturità del software che lessi in un articolo, l'autore confrontava l'elettronica con lo sviluppo del software, evidenziava come l'industria informatica, pure avendo un grandissimo potenziale, non aveva ancora la maturità tecnologica e produttiva di altre industrie, come quella elettronica... ad esempio adduceva il salto qualitativo e produttivo introdotto dai circuiti integrati.

Ora confrontiamo, presa sempre da wikipedia, la definizione di Ergonomia con gli elementi che ho evidenziato nella precedente di API: "L'ergonomia, secondo la IEA (International Ergonomics Association), è quella scienza che si occupa dell'interazione tra gli elementi di un sistema (umani e d'altro tipo) e la funzione per cui vengono progettati (nonché la teoria, i principi, i dati e i metodi che vengono applicati nella progettazione ), allo scopo di migliorare la soddisfazione dell'utente e l'insieme delle prestazioni del sistema[1]. In pratica è quella scienza che si occupa dello studio dell'interazione tra individui e tecnologie." (Ho evidenziato punti chiave in grassetto)

In effetti in questi anni abbiamo tutti visto un rapido progresso nell'industria delle applicazioni e abbiamo notato come, ad esempio, la "pacchettizzazione" e il deploy del software con metodologie e strumenti più rubusti abbia portato un vantaggio competitivo, in termini di produttività, integrazione e aggiornamento del software. Ma questo è solo un esempio, altre idee importanti ne hanno permesso l'evoluzione qualitativa: i pattern per lo sviluppo di applicazioni (chi non ha letto Patterns of Enterprise Application Architecture? ;)), i linguaggi di programmazione, i compilatori, gli ambienti di sviluppo, le metodologie e gli strumenti agili, il testing unitario e automatico del software (Unit Test e TDD) ...

Ergonomia nel software

Per esperienza ho verificato o sono arrivato alla conclusione che alcune pratiche e metodologie sono strumenti che possono permetterci di modellare l'ergonomia della nostra API. Premetto che ho una mia filosofia sull'utilizzo delle pratiche e metodologie, queste vanno applicate con il giusto equilibrio e nel giusto contesto, sono quindi contrario all'approcio "dogmatico" nell'uso di una pratica, con "dogmatico" intendo un approccio per il quale sembra che una metodologia sia il vaso di pandora che quando la scopro risolverà tutti i miei problemi di sviluppatore e che dovrà essere usata sempre per ogni riga di codice che scrivo ;) ... ho visto nella realtà progetti fallire applicando questa "fuorviante e pericolosa strategia"! Premesso questo di seguito illustro alcune ideo o strumenti che se usati bene sono utili a sviluppare codice "ergonomico" per i nostri programmatori, spiegandone brevemente vantaggi e applicazione puramente nell'ambito di cui si discute in questo thread.

  • Naming approach

    Il nome che diamo a una classe o a un metodo, in relazione alla struttura e alla gerarchia di oggetti, soprattuto quando deve essere usato da altri (specialmente quando fa parte del contratto pubblico della nostra API) dovrebbe essere pensato con attenzione. Alcuni programmatori pensano sia una perdita di tempo soffermarsi troppo su quest'aspetto, peccato che poi ci si trova classi e librerie con nomi che seguono una logica disomogenea e a volte fuorvianti che richiedono molto tempo e ragionamenti per capire quale metodo devo chiamare per effettuare una certa operazione oppure peggio ancora mi spingono intuitivamente a chiamare quello sbagliato! Non sarà mai possibile applicare una strategia "perfetta" ai nomi che diamo ai nostri "oggetti", visto che la comunicazione e il linguaggio sono aspetti molto complessi, ma un po' di attenzione, di rigore e di riflessione fanno la differenza.

  • Abstraction details

    Così come un cruscotto di una macchina pieno di pulsanti e/o non bene organizzati forse con un eccesso di gruppi, il modo e il livello di astrazione con cui disegnamo il nostro sistema puo influire sulla complessità di comprensione e utilizzo dello strumento.

  • Test Driven Development

    La pratica dello sviluppo guidato dai test o TDD è utile per obbligarci a modellare i contratti pubblici della nostra applicazione prima del loro dettaglio implementativo. In particolare quando penso ai contratti pubblici non mi riferisco solo ai nomi dei metodi ma anche ai nomi delle classi e alle strategie e al dettaglio di astrazione del nostro modello ad oggetti.

  • Convention Over Configuration

    Quando alcuni anni fa, in un grosso progetto in cui lavoravo, decidemmo di adottare nello sviluppo del nostro software alcune soluzioni "enterprise" ci appoggiamo ad una factory open source derivata da java totalmente configuration driven dove era necessario specificare nel file di configurazione tutto il dettaglio di come construire gli oggetti e iniettare le dipendenze, pur valorizzando la potenza di questo livello di configurazione, dovendo lavorare con domini molto complessi, di centania di componenti, il dettaglio della configurazione stava diventando il nostro tallone di achille. Così decidemmo di intervenire, allora analizzai tutto ciò che poteva essere definito implicitamente per convenzione e sviluppando un sistema automatico di configurazione e l'ausilio di attributi nel codice, con un investimento relativemente ridotto abbiamo portato in carreggiata la competitività del prodotto aumentando produttività e qualitità. La "convention over configuration" se applicata bene (identificando con opportuna attenzione le "reali" convenzioni) rende lo strumento software più produttivo e malleabile, in altre parole più ergonomico.

Conclusione

In altre industrie è stato ed è fondamentale lo studio dell'ergonomia, tanto che ormai è un elemento che fa parte del processo di progettazione e realizzazione di qualsiasi nuovo prodotto. Non sarebbe corretto dire che nella nostra industria non si tenga conto di quest'aspetto, in effetti fa implicitamente parte di diverse pratiche e pattern di sviluppo, ed emerge in alcune librerie di successo, ma non è certamente definito in modo rigoroso come pratica e/o all'interno forse di un approccio metodologico più ampio (che non possa essere quello agile? :D)

Spero che queste riflessioni possona produrre maggiore sensibilità da parte di programmatori, archittetti e responsabili tecnici a quest'aspetto che ho definito 'API ergonomics' soprattuto nell'ambito di progettazione e sviluppo di librerie e strumenti che dovranno usare altri sviluppatori. Sarebbe utile individuare in modo più rigoroso le pratiche, le metodologie, gli strumenti che sono utili allo sviluppo di librerie applicative più ergonomiche, tenendo conto che anch'esse a loro volta, dovrebbero rispondere a criteri di ergonomia. Sarebbe interessante anche poter misurare il livello di ergonomia di una libreria o di una applicazione defininendo e applicando oppurtune metriche (ad esempio come possiamo dare un indice di complessità al codice applicando determinati algoritmi)

E' possibile che per alcuni questa disquisizione possa sembrare mera filosofia, in realtà dal mio punto di vista la preoccupazione di scegliere, o progettare e sviluppare, strumenti più facili e comprensibili da usare per la realizzazione di un progetto software da un vantaggio competitivo e tecnologico non indifferente. Io ho vissuto questa esperienza occupandomi dello sviluppo e della manutenzione di una libreria "enterprise" per un grosso progetto gestionale e ho visto che lavorando nell'applicare pratiche per rendere più intuitivo ed efficacie l'utilizzo dell'infrastruttura al programmatore ne hanno beneficiato in maniera effettiva produttività e qualità.

author: Marco Baldessari | posted @ Sunday, January 18, 2015 4:42 PM | Feedback (0)

Verificare se un evento è già registrato


Problema

Verificare se un evento "incapsulato" ( public event ... ) è già registrato dato l'oggetto origine che genera ed incapsula l'evento, l'oggetto di destinazione che ha registrato l'evento e il nome dell'evento.

Soluzione

Code Snippet
  1. public static bool IsEventAlreadyRegistered<T>(T source, object target, string eventName) where T : class
  2. {
  3.     if (source == null)
  4.         throw new ArgumentNullException("source");
  5.     if (target == null)
  6.         throw new ArgumentNullException("target");
  7.  
  8.     return (typeof (T)
  9.         .GetEvents()
  10.         .Where(@event => @event.Name == eventName)
  11.         .Select(@event => source.GetType().GetField(@event.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
  12.         .Select(fi => fi.GetValue(source)))
  13.         .OfType<MulticastDelegate>()
  14.         .Any(@delegate => @delegate.GetInvocationList()
  15.             .Any(_delegate => _delegate.Target == target));
  16. }

Esempio

Code Snippet
  1. [Test]
  2. public void WhenEventIsMappedIsEventAlreadyRegisteredReturnTrue()
  3. {
  4.     var source = new EventSourceStub();
  5.     source.SomeEvent += ReflectionExtensionsEventTest_MyEvent;
  6.     Assert.IsTrue(ReflectionExtensions.IsEventAlreadyRegistered(source, this, "SomeEvent"));
  7. }
  8.  
  9. void ReflectionExtensionsEventTest_MyEvent(object sender, EventArgs e)
  10. {
  11. }

Vedi progetto FxCommon sorgente ReflectionExtensions.cs test ReflectionExtensionsTest

author: Marco Baldessari | posted @ Wednesday, December 3, 2014 4:44 PM | Feedback (0)

Il mio primo progetto OpenSource (GitHub & NuGet)


  • Ho creato il repository del progetto GIT su GitHub applicando la licenza Apache 2.0

  • Ho quindi clonato il nuovo repository in locale con SourceTree


  • Ho effettuato il merge da un precedente repository git dei progetti FxCommon e FxCommonTest da cartelle diverse
  • Sono partito da questo post Git: Copy a file or directory from another repository preserving the history
  • Merge progetto FxCommon
        mkdir /c/temp/mergepatchs
        export reposrc=/c/Sviluppo/Libreria/FxCommon
        git format-patch -o /temp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
        cd /c/Sviluppo/FxCommon
        git am /temp/mergepatchs/*.patch
        
    Merge progetto FxCommon
        mkdir /c/temp/mergepatchs
        export reposrc=/c/Sviluppo/Tests/FxCommonTest
        git format-patch -o /c/temp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
        cd /c/Sviluppo/FxCommon
        git am /temp/mergepatchs/*.patch
        
  • Ho creato la soluzione VS.2013 vuota e aggiunti i due progetti di Sviluppo e Test
  • Ho preparato una build rudimentale del pacchetto NuGet
    1. Ho configurato le proprieta' del nuovo progetto (Properties/AssemblyInfo.cs)
    2. Ho creato la cartella /Build sotto la root ed aggiunto il file NuGet FxCommon.nuspec
                  <?xml version="1.0" encoding="utf-8" ?>
                  <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
                      <metadata>
                          <id>MBaldessari.FxCommon</id>
                          <version>0.0.1</version>
                          <title>FxCommon</title>
                          <description>.NET core library used by FxXXX projects</description>
                          <authors>Marco Baldessari</authors>
                          <language>en-US</language>
                          <projecturl>https://github.com/waldrix/FxCommon</projecturl>
                          <tags>fx</tags>
                      </metadata>
                  </package> 
          
    3. Ho quindi creato un batch denominato Build.bat
                  mkdir .\bin
                  del .\bin\*.nupkg
                  ..\.nuget\nuget.exe pack ..\FxCommon\FxCommon.csproj -Build -Properties Configuration=Release -OutputDirectory .\bin\
                  pause        
              
  • Effettuato l'upload su NuGet (triviale) da Upload Your Package


    Nota: Non ha preso l'url del progetto dal pacchetto (mi chiedo il perche'??), ma e' stato possibile modificare le proprieta' prima di confermare il pacchetto

  • Il gioco e' fatto! ... ecco il mio pacchetto visibile su NuGet, e' solo una libreria di base con pochissima roba ma comunque "testata", più avanti vorrei pubblicare una libreria più significativa! :)

author: Marco Baldessari | posted @ Monday, December 1, 2014 9:57 PM | Feedback (5)

Re[studing] C Sharp 5 - (Delegates, Events, Lambda expressions, Exceptions, Enumerator, Nullable)


(Vedi Re[studing] C# 5 - (WinRT))
  • Delegates

    • 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

  • Events

    • 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
    1.         class Broadcaster
    2.         {
    3.             public event EventHandler<EventArgs> EventOccured;
    4.  
    5.             public void InvokeOnEventOccurred()
    6.             {
    7.                 OnEventOccured();
    8.             }
    9.  
    10.             public Task InvokeOnEventOccurredAsync()
    11.             {
    12.                 return Task.Factory.StartNew(OnEventOccured);
    13.             }
    14.  
    15.             protected virtual void OnEventOccured()
    16.             {
    17.                 var eventOccured = EventOccured;
    18.                 if (eventOccured != null)
    19.                     EventOccured(this, EventArgs.Empty);
    20.             }
    21.         }

  • Lambda expressions

    • 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
    1.         [Test]
    2.         public void TestOnCapturedVariable()
    3.         {
    4.             var exprs = new Func<int>[3];
    5.             
    6.             for (int i = 0; i < 3; i++)
    7.                 exprs[i] = () => i;
    8.  
    9.             Assert.AreEqual(3, exprs[0]());
    10.             Assert.AreEqual(3, exprs[1]());
    11.             Assert.AreEqual(3, exprs[2]());
    12.         }

  • Exceptions

    • 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
    1.         [Test]
    2.         public void TestDifferentStackTraceOnExceptionThrown()
    3.         {
    4.             try
    5.             {
    6.                 CaptureExceptionAndRethrowWithoutParameter();
    7.             }
    8.             catch (Exception ex)
    9.             {
    10.                 Console.WriteLine("-----CaptureExceptionAndRethrowWithoutParameter-----");
    11.                 Console.WriteLine(ex.StackTrace);
    12.                 Console.WriteLine("----------------------------------------------------");
    13.                 Console.WriteLine();
    14.             }
    15.  
    16.             try
    17.             {
    18.                 CaptureExceptionAndRethrowWithParameter();
    19.             }
    20.             catch (Exception ex)
    21.             {
    22.                 Console.WriteLine("-----CaptureExceptionAndRethrowWithParameter-----");
    23.                 Console.WriteLine(ex.StackTrace);
    24.                 Console.WriteLine("-------------------------------------------------");
    25.                 Console.WriteLine();
    26.             }
    27.         }
    28.  
    29.         void CaptureExceptionAndRethrowWithoutParameter()
    30.         {
    31.             try
    32.             {
    33.                 NotImplementedMethod();
    34.             }
    35.             catch (Exception ex)
    36.             {
    37.                 throw;
    38.             }
    39.         }
    40.  
    41.         void CaptureExceptionAndRethrowWithParameter()
    42.         {
    43.             try
    44.             {
    45.                 NotImplementedMethod();
    46.             }
    47.             catch (Exception ex)
    48.             {
    49.                 throw ex;
    50.             }
    51.         }
    52.  
    53.         void NotImplementedMethod()
    54.         {
    55.             throw new NotImplementedException();
    56.         }

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

  • Enumerator (IEnumerator) e Enumerable (IEnumerable)

    • 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
    1.         static int GeneratedNumbers = 0;
    2.  
    3.         [Test]
    4.         public void EnumerateFirstHundredIntegerNumbers()
    5.         {
    6.             GeneratedNumbers = 0;
    7.             foreach (var number in IntNumbers)
    8.                 if (number == 99)
    9.                     break;
    10.             Assert.AreEqual(100,GeneratedNumbers);
    11.         }
    12.  
    13.         IEnumerable<int> IntNumbers
    14.         {
    15.             get
    16.             {
    17.                 for (int i = 0; i < int.MaxValue;                  i++)
    18.                 {
    19.                     GeneratedNumbers++;
    20.                     yield return i;
    21.                 }
    22.             }
    23.         }

  • Nullable<T>

    • 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

author: Marco Baldessari | posted @ Saturday, October 12, 2013 7:42 PM | Feedback (0)

.NET Composite Formatting


Forse una delle funzionalità più importanti e utili ma anche potenti è flessibili è la possibilità di rappresentare attraverso "stringhe composite" (cosidette composite string) le regole di formatting da un tipo .NET verso una stringa e viceversa nel caso del parsing.

Di seguito sintetizzo in forma tabellare regole e riferimenti alle specifiche dettagliate, compresi gli utilissimi link verso le tabelle che definiscono le stringhe di formattazione.


Composite Formatting Sintassi/Regole/Tabelle per implementare le stringhe composte di formattazione
{n[,[-]w][:f]} Sintassi generale elemento di formattazione
n Obbligatorio: "n" è l'indice della variabile il cui valore è da formattare
[,[-]w] Opzionale: "[,[-]w]" indica la larghezza minima del testo formattato
  • Se positivo e la lunghezza della stringa formattata è minore di w allora vengono aggiunti all'inizio tanti spazi quanti w (corrisponde ad effettuare un PadLeft() su un tipo string)
  • Se negativo e la lunghezza della stringa formattata è minore di w allora vengono aggiunti in fondo tanti spazi quanti w (corrisponde ad effettuare un PadRight() su un tipo string)
[:f] Opzionale: "[:f]" indica la stringa di formattazione
Code Snippet
  1.         [Test]
  2.         public void UseOfMinimumWidthInStringFormatItem()
  3.         {
  4.             var d = 10.5M;
  5.  
  6.             Assert.AreEqual("10.5", string.Format(CultureInfo.InvariantCulture, "{0}", d));
  7.             Assert.AreEqual("      10.5", string.Format(CultureInfo.InvariantCulture, "{0,10}", d));
  8.             Assert.AreEqual("10.5".PadLeft(10), string.Format(CultureInfo.InvariantCulture, "{0,10}", d));
  9.             Assert.AreEqual("10.5      ", string.Format(CultureInfo.InvariantCulture, "{0,-10}", d));
  10.             Assert.AreEqual("10.5".PadRight(10), string.Format(CultureInfo.InvariantCulture, "{0,-10}", d));
  11.         }
Stringhe di formattazione Standard Risolvono problemi generici di formattazione, consistono di una lettera opzionalmente seguita da un numero indicate il numero dei digit da applicare in formattazione
Standard Numeric Format Strings Tabella stringhe standard di formattazione per i tipi numerici
Standard Date and Time Format Strings Tabella stringhe standard di formattazione per i tipi data e ora
Code Snippet
  1.         [Test]
  2.         public void UseOfStandardFormatStringHonoringLocalCultureIt()
  3.         {
  4.             var d = 1250.75M;
  5.             var dt = new DateTime(2010, 1, 1, 15, 30, 30);
  6.  
  7.             Assert.AreEqual("          € 1.250,75", string.Format("{0,20:C}", d));
  8.             Assert.AreEqual("01/01/2010 15:30    ", string.Format("{0,-20:g}", dt));
  9.         }
Stringhe di formattazione Custom Per formattare in modo preciso e puntuale ogni carattare secondo un template
Custom Numeric Format Strings Tabella stringhe custom di formattazione per i tipi numerici
Custom Date and Time Format Strings Tabella stringhe custom di formattazione per i tipi data e ora
Code Snippet
  1.         [Test]
  2.         public void UseOfCustomFormatString()
  3.         {
  4.             var d = 1250.75M;
  5.             var dt = new DateTime(2010, 1, 1, 15, 30, 30);
  6.  
  7.             Assert.AreEqual("      eur 001.250,75", string.Format("{0,20:eur 000,000.00}", d));
  8.             Assert.AreEqual("1-1-10 15:30.30     ", string.Format("{0,-20:d-M-yy HH:mm.ss}", dt));
  9.         }
Enumerazioni di stili Utilizzati per il parsing delle stringhe per i tipi numerici o data e ora
NumberStyles Enumeration Enumerazione di stili per il parsing di tipi numerici
DateTimeStyles Enumeration Enumerazione di stili per il parsing di tipi data e ora
Code Snippet
  1.  
  2.         [Test]
  3.         public void ParsingStringHonoringLocalCultureItAndUsingSpecificStyles()
  4.         {
  5.             var d = 1250.75M;
  6.             var dt = new DateTime(2010, 1, 1, 15, 30, 00);
  7.  
  8.             Assert.AreEqual(d, decimal.Parse("          € 1.250,75", NumberStyles.Currency));
  9.             Assert.AreEqual(dt, DateTime.Parse("01/01/2010 15:30    ", CultureInfo.CurrentCulture,
  10.                                                             DateTimeStyles.AllowWhiteSpaces));
  11.         }
System.Globalization.CultureInfo.CurrentCulture Cultura corrente: Nella maggioranza dei casi formatting & parsing si onora la cultura corrente
System.IFormatProvider Fornisce informazioni per la formattazione, la maggioranza dei metodi di formatting & parsing implementano overload che dove è possibile passarlo come parametro
System.Threading.Thread.CurrentCulture Per ottenere e/o cambiare la cultura del thread Corrente
System.Globalization.CultureInfo.DefaultThreadCurrentCulture Per ottenere e/o cambiare la cultura di default per tutti i threads (solo .NET 4.5)
System.Globalization.CultureInfo.InvariantCulture Cultura invariante rispetto all'ambiente di esecuzione (NOTA: basata sulla cultura americana)
Code Snippet
  1.         [Test]
  2.         public void UseOfStandardFormatStringPassingUsingSpecifingCulture()
  3.         {
  4.             var d = 1250.75M;
  5.             var dt = new DateTime(2010, 1, 1, 15, 30, 30);
  6.  
  7.             CultureInfo it = new CultureInfo("it-IT");
  8.             CultureInfo us = new CultureInfo("en-US");
  9.  
  10.             Assert.AreEqual("          € 1.250,75", string.Format(it, "{0,20:C}", d));
  11.             Assert.AreEqual("01/01/2010 15:30    ", string.Format(it, "{0,-20:g}", dt));
  12.             Assert.AreEqual("           $1,250.75", string.Format(us, "{0,20:C}", d));
  13.             Assert.AreEqual("1/1/2010 3:30 PM    ", string.Format(us, "{0,-20:g}", dt));
  14.         }

author: Marco Baldessari | posted @ Tuesday, October 8, 2013 1:02 PM | Feedback (0)

C# Unexpected recursion due to events


Premessa

E' cosa nota per chi programma in Windows Forms (ma non solo...) situazioni di inaspettata ricorsione in fase di gestione degli eventi. Di solito capita quando un gestore di un evento, o indirettamente e più facilmente, un metodo comune chiamato da quest'ultimo, modifica un controllo UI che a sua volta scatena un nuovo evento il cui gestore invoca lo stesso codice... ed ecco la situazione di ricorsione imprevista che nella maggioranza dei casi porta ad avere una "fastidiosa" ;) StackOverflowException!

Soluzione

Ci sono varie soluzioni al problema, oltre a riprogettare il gestore per evitare queste situazioni, un metodo potrebbe essere quello di deregistrare temporaneamente gli eventi sul controllo che dobbiamo modificare, anche se quest'ultimo approccio non mi piace e di solito non lo utilizzo perché troppo macchinoso. Io preferisco utilizzare una variabile di classe che mi dice se sto già eseguendo quella parte di codice. Di seguito riporto un esempio.

Code Snippet
  1.         bool m_inUseVariableForPreventingUnexpectedRecursion;
  2.  
  3.         [Test]
  4.         public void UseVariableForPreventingUnexpectedRecursion()
  5.         {
  6.             if (m_inUseVariableForPreventingUnexpectedRecursion)
  7.                 return;
  8.  
  9.             try
  10.             {
  11.                 m_inUseVariableForPreventingUnexpectedRecursion = true;
  12.  
  13.                 //Some code ...
  14.  
  15.                 //Generate recursion ..
  16.                 UseVariableForPreventingUnexpectedRecursion();
  17.             }
  18.             finally
  19.             {
  20.                 m_inUseVariableForPreventingUnexpectedRecursion = false;
  21.             }
  22.         }

Ho pensato però di ridurre questo "overhead" progettando una classe, l'idea alla base è quella di utilizzare un dizionario per registare le invocazioni di metodi legati ad istanze di classe, e l'interfaccia IDisposable per racchiudere il tutto in un elegante statment using. Ecco il risultato:

Code Snippet
  1.         [Test]
  2.         public void UseEnterMonitorClassForPreventingUnexpectedRecursion()
  3.         {
  4.             using (var mon = new EnterMonitor(this, "UseEnterMonitorClassForPreventingUnexpectedRecursion"))
  5.             {
  6.                 if (mon.IsAlredyEntered)
  7.                     return;
  8.  
  9.                 //Some code ...
  10.  
  11.                 //Generate recursion ..
  12.                 UseEnterMonitorClassForPreventingUnexpectedRecursion();
  13.             }
  14.         }

Di seguito vi riporto il codice della classe: (NOTA: Ci sono due miglioramenti che voglio introdurre successivamente: 1. Sincronizzazione per funzionare in ambito di concorrenza 2. Tipizzare il nome del metodo usando una Lambda Expression...

Code Snippet
  1.     public class EnterMonitor : IDisposable
  2.     {
  3.         readonly object m_obj;
  4.         readonly string m_name;
  5.         bool m_thisExited;
  6.  
  7.         static readonly Dictionary<object, Dictionary<string, int>> Counters = new Dictionary<object, Dictionary<string, int>>();
  8.  
  9.         public EnterMonitor(object obj, string name)
  10.         {
  11.             m_obj = obj;
  12.             m_name = name;
  13.  
  14.             RegisterEnter();
  15.         }
  16.  
  17.         public void Dispose()
  18.         {
  19.             RegisterExit();
  20.         }
  21.  
  22.         public bool IsAlredyEntered { get { return SafeGetCounter() > 1; } }
  23.         public bool IsExited { get { return SafeGetCounter() == 0; } }
  24.  
  25.         void RegisterEnter()
  26.         {
  27.             Dictionary<string, int> counter;
  28.  
  29.             if (!Counters.TryGetValue(m_obj, out counter))
  30.             {
  31.                 counter = new Dictionary<string, int>();
  32.                 Counters[m_obj] = counter;
  33.             }
  34.  
  35.             int count;
  36.  
  37.             if (!counter.TryGetValue(m_name, out count))
  38.                 counter[m_name] = 0;
  39.  
  40.             counter[m_name]++;
  41.         }
  42.  
  43.         void RegisterExit()
  44.         {
  45.             if (m_thisExited)
  46.                 return;
  47.  
  48.             Counters[m_obj][m_name]--;
  49.  
  50.             //Remove entry for object where all counters is zero for prevent Memory Leak
  51.             if (Counters[m_obj].All(p => Counters[m_obj][p.Key] == 0))
  52.                 Counters.Remove(m_obj);
  53.  
  54.             m_thisExited = true;
  55.         }
  56.  
  57.         int SafeGetCounter()
  58.         {
  59.             Dictionary<string, int> counter;
  60.  
  61.             if (!Counters.TryGetValue(m_obj, out counter))
  62.                 return 0;
  63.  
  64.             int count;
  65.  
  66.             if (!counter.TryGetValue(m_name, out count))
  67.                 return 0;
  68.  
  69.             return counter[m_name];
  70.         }
  71.     }

NOTA: TryGetValue(...) è il metodo più performante per verificare se un entry esiste in un dizionario in quanto evita situazioni di doppio matching ...

author: Marco Baldessari | posted @ Wednesday, September 18, 2013 8:47 AM | Feedback (0)

Re[studing] C# 5 - (const, static readonly, partial methods, inheritance, structs, internal, flags enum, generics, covariance and controvariance)


(Vedi Re[studing] C# 5 - (WinRT))
  • const versus static readonly

    Un campo dichiaranto come costante (const) è valutato staticamente in fase di compilazione e sostituto nel codice. Per questa ragione vanno dichiarati constanti i valori o le stringhe realmente immutabili nel tempo, pena side-effects non voluti nel caso di aggiornamento di un Assembly dipendente senza ricompilare, in questi casi definire il campo static readonly, .

  • Partial methods (C# 3.0)

    Utili nell'utilizzo di classi generate automaticamente quando si ha la necessità di modularizzare parti di implementazione da parte dell'utente, infatti la classe è compilabile anche quando il metodo non viene implementato, in questo caso la chiamata del metodo non viene generata. Il metodo deve essere privato e non deve avere tipo di ritorno (void).

    Code Snippet
    1.         partial class PartialExample
    2.         {
    3.             public PartialExample()
    4.             {
    5.                 CustomInitObject(); //If not implemented the call is not generated
    6.             }
    7.  
    8.             partial void CustomInitObject();
    9.         }
    10.  
    11.         partial class PartialExample
    12.         {
    13.             partial void CustomInitObject()
    14.             {
    15.                 //Implementation ...
    16.             }
    17.         }

  • class and inheritance

    • class A : B: A è la sottoclasse (o subclass), B è la superclasse (o superclass)
    • La conversione di una referenza di tipo A in una di tipo B è definita upcast ed è implicita
    • La conversione di una referenza di tipo B in una di tipo A è definita downcast e deve essere esplicita

  • structs

    • Le struct sono tipi-valore, per la precisione sono di classe System.ValueType, e di conseguenza implicitamente object
    • Le struct non supportano i parameterless constructor, finalizers e virtual members

    Code Snippet
    1.         struct S
    2.         {
    3.         }
    4.     
    5.         [Test]
    6.         public void TestStruct()
    7.         {
    8.             S s = default(S);
    9.  
    10.             Assert.IsInstanceOf<object>(s);
    11.             Assert.IsInstanceOf<ValueType>(s);
    12.         }

  • internal access modifier

    • Il modificatore internal rende visibili i tipi solo all'interno dello stesso assembly
    • I tipi internal possono anche essere visibili a friend assemblies utilizzando l'attributo InternalsVisibleToAttribute

  • flags enum

    • Quando un tipo enum è dichiarato con l'attributo [Flags], i suoi valori possono essere combinati con gli operatori bit a bit (come | e &)
    • La chiamata a ToString() su di un enum di tipo Flags produce l'elenco del nome dei valori separati da "," quando c'è corrispondenza altrimenti solo un numero

    Code Snippet
    1.         [Flags]
    2.         enum Options
    3.         {
    4.             One = 1,
    5.             Two = 2,
    6.             Three = 4
    7.         }
    8.  
    9.         [Test]
    10.         public void TestEnumFlags()
    11.         {
    12.             var o = Options.One | Options.Three;
    13.             Assert.AreEqual("One, Three", o.ToString());
    14.             var o2 = (Options) 6;
    15.             Assert.AreEqual("Two, Three", o2.ToString());
    16.             var o3 = (Options) 8;
    17.             Assert.AreEqual("8", o3.ToString());
    18.         }

  • Generics: Naked Type constraint (vedi C# Naked Type constraint)

    Un argomento di un tipo generic può essere dichiarato con la constraint where TY:TX indicando che TY deve essere di tipo TX.

    Code Snippet
    1.         class Generic1<TClass, TSubClass> where TSubClass : TClass
    2.         {
    3.             public bool IsTClassAssegnabileFromTSubClass {get { return typeof (TClass).IsAssignableFrom(typeof (TSubClass)); }}
    4.         }
    5.  
    6.         class A { }
    7.         class B:A { }
    8.  
    9.         [Test]
    10.         public void TestGenericUseWithNakedType()
    11.         {
    12.             var generic1 = new Generic1<object, A>();
    13.             var generic2 = new Generic1<A, B>();
    14.             //var generic3 = new Generic1<B, A>(); Compile error
    15.  
    16.             Assert.IsTrue(generic1.IsTClassAssegnabileFromTSubClass);
    17.             Assert.IsTrue(generic2.IsTClassAssegnabileFromTSubClass);
    18.         }

  • Generics: Self referencing declarations

    Un argomento di un tipo generic può essere dichiarato dello stesso tipo della classe, obbligando di fatto ad effettuare un subclassing per poterlo istanziare.

    Code Snippet
    1.         class SelfGeneric<T> where T : SelfGeneric<T> { }
    2.         class SubSelfGeneric : SelfGeneric<SubSelfGeneric> {}
    3.  
    4.         [Test]
    5.         public void TestGenericUseWithSelfType()
    6.         {
    7.             var generic1 = new SelfGeneric<SubSelfGeneric>();
    8.             var generic2 = new SubSelfGeneric();
    9.             //var generic3 = new SelfGeneric<object>(); //Compile error
    10.         }

  • Generics: Open/Close Type e differenze con i template C++

    • Un generic nella forma class<T> è definito un open type (che appunto è un modello di classe)
    • Quando dichiarato con il parametro, ad esempio class<int> è definito un close type (tipo concreto ed instanziabile a runtime)
    • In c++ il close type viene generato a compile time per questo le librerie di template c++ (ad es. ATL,STL) sono rilasciate come sorgente

  • Covariance and controvariance

    • Nel caso di A : B (A è implicitamente convertibile in B - vedi upcast) allora I<A> è implicitamente convertibile in I<B>, quest'ultima caratteristica è detta covarianza (covariance)
      • La covarianza è applicabile per le interfacce generic, i delegati generic, gli array, ma NON per le classi generic, questo nel rispetto della static type safety c#
      • Nelle interfacce generic la covarianza è supportata solo per i parametri modificati con out, esempio IProducer<out T> { T Produce(); }
        • Il modificatore out indica che T può essere indicato solo come parametro di output nel contratto dell'interfaccia
    • Quando I<B> è implicitamente convertibile in I<A> allora si parla di controvarianza (controvariance)
      • Questa caratteristica è possibile solo definendo un interfaccia controvariante, dove uno o più paremtri sono indicati con il modificatore in.
      • In questo caso il parametro decorato con il modificatore in può comparire solo come input nel contratto dell'intefaccia, esempio IConsumer<in T> { void Consume(T value); }

    NOTA: Stiamo parlando di interfacce cioè di contratti e non di tipi concreti, in questo caso la covarianza e la controvaianza sono meccanismi avanzati che ci permettono di dichiarare contratti che ci garantiscono il controlo sui tipi a compile tyme (static type safety). Riprendendo gil esempi sopra riportati (produttore, consumatore), nel caso della covarianza, stiamo dichiarando che un produttore ha un metodo che ritorna l'istanza di un oggetto di tipo T: dato un tipo concreto, ad esempio A (sottoclasse di B) possiamo sempre convertire implicitamente questo tipo nella sua superclasse (in B o in object), ecco perché IProducer<A> sarà convertibile in IProducer<B> ma NON vieceversa. In realtà quando parliamo di controvarianza stiamo facendo fondamentalmente la stessa operazione ma in modo simmetrico, infatti stiamo dichiarando che un consumatore ha un metodo che prende in input un oggetto di tipo T: dato un tipo concreto, ad esempio B (classe base) possiamo sempre convertire A (sottoclasse) in B ed è per questo che IConsumer<B> è convertibile in IConsumer<A> ma NON viceversa.

author: Marco Baldessari | posted @ Wednesday, August 28, 2013 5:09 PM | Feedback (0)

Passaggio a Git


Ho deciso di passare a Git per il repository della mia code base privata. Di seguito un breve log dei principali step della migrazione.

  • Conoscevo Git in linea teorica ma non l'avevo mai usato (sono un utilizzatore di SVN da molti anni ...) quindi ho scelto queste risorse per acquisire un minimo known-out iniziale:
  • Scelto un servizio di host: Volevo provare GitHub, sul quale mi sono registrato, ma purtroppo con l'account gratuito è possibile creare solo repository pubblici, così ho optato per il servizio di hosting offerto Assembla. (GitHub l'ho utilizzerò in seguito per pubblicare un eventuale progetto open source...)
  • Ho scelto come client windows GitHub for Windows, senza per il momento utilizzare/configurare la connessione verso il portale GitHub.
  • Ho creato cercato di creare il mio primo repository facendo un clone dal repository privato inizializzato su Assembla, ma prima era necessario risolvere un problema di sicurezza, in quanto il comando git clone falliva con un errore di sicurezza ...
    • Ho seguito la soluzione trovata in questo post permission denied git push FATAL: COULD NOT READ FROM REPO: In altre parole ho dovuto creare, nel profilo locale, un certificato per la crittografia SSH con l'utility ssh-keygen e installare la chiave pubblica nel profilo di Assembla (NOTA: tutte le operazione a riga di comando le ho eseguite dalla powershell di GitHub for Windows)
    • Ho quindi potuto eseguire con successo il clone del mio repository in locale (vuoto) con il comando git clone git@git.assembla.com:xxx.yyy.git

  • Ho fatto la mia prima commit Git e quindi sincronizzato il mio repository (effettuando la mia prima push :))
  • Volevo copiare il mio repository SVN mantenendo tutta la storia delle commit. Dopo aver fatto parecchie ricerche su Internet ...
    • Ho clonato il repository SVN trasformando in un repository Git con il comando git svn clone [SVN repo URL]
    • Ho seguito, in parte, i suggerimenti di questo post How to import existing GIT repository into another?: ho creato un branch sul mio nuovo repository Git, ho importato tutto il repository Git clonato da SVN, ho mergiato il branch sul trunk e quindi ho aggiornato il server.
Sono solo all'inizio e ho ancora molto da imparare ... però l'esperienza di usare un DVCS quale Git mi entusiasma molto, ora sto iniziando a capire meglio le sue potenzialità quali il Branching, che in Git è "quasi magico" unito alla potenzialità di un repository distribuito.

author: Marco Baldessari | posted @ Monday, July 22, 2013 10:13 PM | Feedback (2)

SVN - Creare una fix partendo da una revision con merge delle sole modifiche in una o più revision successive


Problema

Ogni tanto e "in situazioni di emergenza", mi capita di dover preparare una singola DLL, per la fixare in un produzione un bug. Dato che il nostro gruppo di sviluppo utilizza SVN come repository del codice, con un unico trunk sul quale vengono fatte le build, etichettate con specifica commit da un sistama di build automatico, procedo nel seguente modo:

  • Dalla versione del sw rilasciata in produzione ottengo la revision della build (rev)
  • Effetto una checkout to revision alla rev così determinata
  • Isolo la o le revision che includono la fix: [revfix1], [revfix2] ... dove revfixX>rev
  • Effettuo o una svnupdate sui singoli file oggetto della build o un merge delle revision [revfix1], [revfix2] ...

Ho pensato di dedicare qualche minuto e scrivere un batch (svnfix.bat) che faccia questa operazione in automatico che di seguito riporto:

SET PT=%1
SET REV=%2
SHIFT
SHIFT
svn update %PT% -r %REV%
:start_merge
IF "%1"=="" (GOTO :end_merge)
CD %PT%
svn merge -c %1 %PT%
SHIFT
GOTO :start_merge
:end_merge

L'utilizzo è il seguente: svnfix path_working_copy rev [revfix1] [revfix2] ... ricordatevi di fare una revert dei file mergiati dopo aver buildato ...

PS: So che esistono i workflow che prevedono la creazione di apposite branch per il deploy dalle quale poi sarebbe possibile fare il fix, ma attualmente per varie ragioni questa modalità non è implementata dal nostro gruppo di lavoro.

author: Marco Baldessari | posted @ Tuesday, July 16, 2013 5:55 PM | Feedback (0)