C#

Nullable.Equals(object) … comportamento “strano” che ha una spiegazione !

Ieri, in ufficio con alcuni miei colleghi, mi sono imbattuto in un comportamento alquanto strano del metodo Equals(object) della struct Nullable<T>.

Consideriamo il seguente frammento di codice :

short? x = 1;
bool b = x.Equals(1);

Secondo voi….quanto vale la variabile b ? Ebbene….magicamente false !

A questo punto consideriamo il seguente frammento di codice :

short? x = 1;
// caso 1
bool b = x.Equals(1);
// caso 2
b = x.Equals((short)1);
// caso 3
short y = 1;
b = x.Equals(y);

Il “caso 1” è ovviamente quello appena visto e ritorna “stranamente” b uguale a false. Il “caso 2” e “caso 3” si comportano correttamente fornendo un valore true.

Cambiamo il “caso 1” nel modo seguente :

int? x = 1;
bool b = x.Equals(1);

In questo caso, abbiamo semplicemente modificato la dichiarazione del nullable type dashort? a int? ed il valore di b diventa “magicamente” true !

Ecco che a questo punto, dotandomi di Reflector ho cominciato ad indagare sull’implementazione del metodo Equals(object) della struct Nullable<T> che riporto di seguito.

public override bool Equals(object other)
{
    if (!this.HasValue)
    {
        return (other == null);
    }
    if (other == null)
    {
        return false;
    }
    return this.value.Equals(other);
}

Dopo aver eseguito un paio di controlli preliminari sul fatto che la struct abbia un valore e che l’oggetto passato per il confronto non sia null, viene invocato il metodo Equals(object) del tipo T del campo value (interno alla struct Nullable<T>).

Considerando il caso in cui avevamo la dichiarazione short?, ossia Nullable<short>, il metodo che viene invocato sarà ovviamente Int16.Equals(object) la cui implementazione è la seguente :

public override bool Equals(object obj)
{
    return ((obj is short) && (this == ((short) obj)));
}

L’utilizzo dell’operatore is, per la verifica del tipo del parametro, svela l’arcano !

Come sappiamo, tutti i numeric literals (il numero “1” nel nostro caso) sono trattati implicitamente come Int32, per cui la chiamata x.Equals(1) (dove x è di tipo short?) restituisce “correttamente” false, in quanto obj non sarà short ma int !

In tutti gli altri casi, dichiarando esplicitamente una variabile di tipo short (si veda short y = 1) oppure eseguendo il cast esplicito a short del numeric literal (si veda x.Equals((short)1)), forziamo il tipo (coincidente con short? x) ed il confronto ritorna un valore corretto.

Questo spiega anche il perchè del fatto che modificando la dichiarazione da short? x a int? x, otteniamo un valore corretto per x.Equals(1)….in quanto il numeri literal “1” come già detto è implicitamente un Int32 !

In conclusione…..attenzione all’uso del metodo Equals(object) e dei numeric literals nei confronti !!

Da byte[] a String con il .Net Micro Framework

Spesso, dato un array di byte con codifica UTF8, si rende necessaria la relativa conversione in una stringa. Purtroppo, il .Net Micro Framework non mette a disposizione il metodoEncoding.UTF8.GetString(byte[] bytes) utile per questo scopo.

Il metodo più veloce per ottenere il medesimo risultato è il seguente :

byte[] bytes;
...
...
string s = new String(Encoding.UTF8.GetChars(bytes));

E’, in pratica, necessario ricavere prima l’array di char corrispondenti e poi instanziare un oggetto String inizializzandolo con tale array.

WPF Dispatcher.CheckAccess() vs Windows Forms Control.InvokeRequired

Coloro che utilizzano quotidianamente le Windows Forms conosceranno il significato della property InvokeRequired nell’ambito della classe Windows.Forms.Control. Sappiamo infatti che i controlli di una Windows Forms sono legati ad uno specifico thread (generalmente il main thread) e non sono thread safe. Per questo motivo, se intendiamo invocare un qualsiasi metodo su un controllo oppure modificare una sua property (es. modificare il testo di una label o di una textbox) da un thread differente, è necessario invocare uno dei metodi che permettono di eseguire il marshal nel thread giusto. Questi ultimi sono : Invoke() per l’invocazione sincrona, BeginInvoke() e EndInvoke() per un’invocazione asincrona.

La property InvokeRequired ci permette di capire se è necessario eseguire questa operazione di marshal in un altro thread o meno. Per questo motivo, quando bisogna intervenire tipicamente sulla UI, all’interno del nostro metodo adottiamo un codice di questo tipo :

if (this.myControl.InvokeRequired)
{
    // accesso indiretto attraverso Invoke() o BeginInvoke()
    this.myControl.Invoke(.....);
}
else
{
    // accesso diretto a myControl
    this.myControl.SomeMethod();
    this.myControl.SomeProperty = value;
}

Ma esiste in WPF una controparte della InvokeRequired ?

In WPF, ogni controllo ha la property Dispatcher sulla quale è possibile invocare i medesimi metodi Invoke() e BeginInvoke() per eseguire il marshal nel thread giusto. Sempre Dispatcher fornisce la controparte della InvokeRequired, ossia il metodo CheckAccess() che ritorna true se l’invocazione avviene già nel thread giusto. Per questo motivo il codice suddetto diventa :

if (this.myControl.Dispatcher.CheckAccess())
{
    // accesso diretto a myControl
    this.myControl.SomeMethod();
    this.myControl.SomeProperty = value;
}
else
{
    // accesso indiretto attraverso Invoke() o BeginInvoke()
    this.myControl.Dispatcher.Invoke(.....);
}

E’ importante sottolineare che se in Visual Studio provate a farvi aiutare dall’Intellisense, il metodo CheckAccess() non vi verrà mostrato ma se lo scrivete senza alcun aiuto e compilate, tutto funzionerà correttamente. La sua invisibilità è dovuta alla sua dichiarazione :

[EditorBrowsable(EditorBrowsableState.Never)]
public bool CheckAccess();

Il gate per salire a bordo del Kinect : la classe Runtime

Tra tutte le classi messe a disposizione dal Kinect SDK, quella sicuramente più importante è la classe Runtime. Attraverso quest’ultima è possibile gestire tutti i sottosistemi, quali la video camera, il sensore di profondità ed il riconoscitore della figura umana. Di seguito riporto il relativo class diagram :

6153.Runtime-Class_56547733

Inizializzazione e deinizializzazione

Per poter utilizzare il Kinect, la prima cosa da fare è istanziare la classe Runtime.

nui = new Runtime();
Oltre al costruttore di default, senza alcun parametro, è previsto un suo overload che ha in ingresso un index che rappresenta l’identificativo del Kinect da inizializzare nel caso in cui al PC ve ne fosse collegato più di uno. Tale indice è inoltre accessibile successivamente attraverso la property InstanceIndex di sola lettura.
Successivamente, è necessario eseguirne l’inizializzazione, in modo che vengano allocate tutte le risorse unmanaged che sono necessarie per utilizzare i sottosistemi del dispositivo stesso. Per fare ciò, è necessario invocare il metodo Initialize().
nui.Initialize(RuntimeOptions.UseColor | RuntimeOptions.UseDepth);
Tale metodo prevede in ingresso un parametro di tipo RuntimeOptions, ossia un enumerativo attraverso il quale specificare quali sensori vogliamo utilizzare. Ovviamente, i valori possibili possono essere messi in OR tra loro in modo da utilizzare più sensori contemporaneamente. Più precisamente, sono i seguenti :
  • UseDepthAndPlayerIndex : utilizzo del sensore di profondità con riconoscimento del giocatore (ciascuna giocatore viene individuato attraverso un index diverso);
  • UseColor : utilizzo della video camera;
  • UseSkeletalTracking : riconoscimento della figura umana di ciascun giocatore;
  • UseDepth : utilizzo del sensore di profondità;
Una volta inizializzato ed utilizzato, l’istanza della Runtime va deinizializzata attraverso l’utilizzo del metodo Uninitialize() per poter permettere la deallocazione di tutte le risorse unmanaged.
nui.Uninitialize();
Gli stream, lo skeleton e la video camera
La classe Runtime dispone di alcune proprietà attraverso le quali è possibile accedere a tutte le funzionalità del Kinect.
In primo luogo, vi sono i due seguenti stream :
  • VideoStream : rappresenta lo stream della video camera, fornendo allo sviluppatore i fotogrammi ripresi da quest’ultima;
  • DepthStream : rappresenta lo stream del sensore di profondità, in cui ciascun pixel non esprime un punto di un’immagine (in termini RGB, come nel caso del VideoStream) ma fornisce l’informazione della distanza di quel punto rispetto al Kinect;

Entrambe le property rappresentano un riferimento alla classe ImageStream.

Per poter utilizzare tali stream, è necessario la loro apertura invocando su ciascuno di essi il relativo metodo Open() della classe ImageStream. Tale metodo prevede i seguenti parametri :

  • streamType : di tipo ImageStreamType che permette di dichiarare il tipo di stream al quale siamo interessati (Invalid, Depth o Video);
  • poolSize : dimensione del buffer (espresso in numero di frame) che il runtime dovrà utilizzare. Tipico valore è 2;
  • resolution : di tipo ImageResolution che indica la risoluzione da adottare per lo stream;
  • image : di tipo ImageType che indica il tipo di contenuto nell’immagine. Alcuni possibili valori sono ad esempio Depth per indicare un’immagine che contiene informazioni sulla distanza, Color per indicare un’immagine ripresa dalla video camera in formato RGB oppure ColorYuv per indicare un’immagine ripresa dalla video camera in formato YUV;
nui.VideoStream.Open(ImageStreamType.Video, 2,
ImageResolution.Resolution640x480, ImageType.Color);
nui.DepthStream.Open(ImageStreamType.Depth, 2,
ImageResolution.Resolution640x480, ImageType.Depth);

Per accedere al motore per il riconoscimento della figura umana, è disponibile la propertySkeletonEngine che rappresenta un riferimento alla classe SkeletonEngine. Infine, attraverso la property NuiCamera si ottiene un riferimento alla classe Camera, con la quale poter accedere alla video camera e modificarne per esempio l’inclinazione.

Intercettazione delle frame : gli eventi

Per evitare un utilizzo in modalità polling del Kinect, la classe Runtime mette a disposizione alcuni eventi che vengono sollevati quando sono disponibili nuove frame dalla video camera, dal sensore di profondità e dal riconoscitore della figura umana.

nui.VideoFrameReady += new EventHandler<ImageFrameReadyEventArgs>(nui_VideoFrameReady);
nui.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(nui_DepthFrameReady);
nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

Essi rappresentano :

  • VideoFrameReady : sollevato quando è disponibile, nel buffer, una frame dello stream video (VideoStream);
  • DepthFrameReady : sollevato quando è disponibile, nel buffer, una frame dello stream di profondità (DepthStream);
  • SkeletonFrameReady : sollevato quando è disponibile un’immagine della figura umana rilevata dallo skeleton engine;

Kinect : il dispositivo, l’installazione dell’SDK ed i primi passi

Tutti i possessori o meno di una console XBOX sono a conoscenza dell’esistenza del Kinect, un fantastico dispositivo attraverso il quale la Microsoft ha iniziato a fare concorrenza alla Nintendo Wii, relativamente ai giochi che non prevedono l’utilizzo di un normale gamepad ma che si basano sui movimenti del corpo del giocatore.

Va sottolineato, però, che nel caso del Kinect il solo uso con la XBOX è piuttosto riduttivo ed infatti la Microsoft ha rilasciato ultimamente un SDK attraverso il quale poter sviluppare delle applicazioni che prevedono l’utilizzo del dispositivo collegandolo al PC.

Prima di poter iniziare ad esplorare gli strumenti messi a disposizione nell’SDK, è utile comprendere le funzionalità del dispositivo e preparare il nostro PC con il relativo ambiente di sviluppo.

Il Kinect visto da vicino

Il Kinect è costituito dai quattro seguenti dispositivi principali :

  • Videocamera RGB (RGB camera) : una normale video camera con una risoluzione di 640 x 480 a 30 fps (frames per second);
  • Sensore di profondità (3D Depth Sensors) : costituito da una trasmettitore ad infrarossi ed il corrispondente ricevitore attraverso i quali è possibile calcolare le distanze degli oggetti;
  • Un motore per l’inclinazione (Motorized Tilt) : un piccolo motore che permette di variare l’inclinazione della video camera fino ad un massimo di 27 gradi (in alto o in basso);
  • Una serie di microfoni (Multi-Array Mic) : una serie di quattro microfoni per l’audio;

6406.kinect_69CB3395

Installazione : SDK e driver Kinect

L’ambiente necessario per lo sviluppo è Visual Studio 2010, almeno nella sua versione Express (nel mio caso è la Ultimate che uso quotidianamente).

Ad esso vanno aggiunti ed installati :

  • Kinect SDK (ovviamente ancora in versione Beta) che possiamo scaricare al seguentelink (in entrambe le versioni 32 o 64 bit);
  • DirectX Software Development Kit che possiamo scaricare al seguente link:

Al termine dell’installazione, per verificare che il tutto sia avvenuto correttamente, possiamo collegare il Kinect al nostro PC attraverso il cavo USB e verificarne la sua presenza attraversoGestione Dispositivi nel Pannello di Controllo.

2703.kinect_device_manager_3DDA940C

Inoltre, il gruppo dei microfoni dovrebbe essere visibile nella scheda dei Dispositivi di Registrazione.

8507.kinect_mic_array_6A171AF0

Per quanto riguarda il cavo di collegamento del Kinect al PC, va sottolineato che quest’ultimo non ha nativamente un connettore USB ma è necessario acquistare un adattatore con relativo alimentatore. Sul Microsoft Store, al seguente link, è riportata la versione per gli Stati Uniti ma è ovviamente possibile acquistare anche la versione Italiana.

Sviluppo : i primi passi

Affrontiamo adesso i primi passi per poter iniziare a sviluppare con il Kinect SDK, introducendo la classe principale di quest’ultimo : la Runtime.

Creiamo un nuovo progetto WPF (ovviamente possiamo utilizzare anche le Windows Forms) e per prima cosa aggiungiamo il riferimento al seguente assembly :Microsot.Research.Kinect.

6457.assembly_2A3D0E9E

Inoltre, per poter utilizzare tutte le classi disponibili nell’SDK è necessario aggiungere lo using dei seguenti namespace.

using Microsoft.Research.Kinect.Audio;
using Microsoft.Research.Kinect.Nui;

Definiamo un campo privato nella classe MainWindow di tipo Runtime ed istanziamolo.

public partial class MainWindow : Window
{
    private Runtime nui = new Runtime();

    public MainWindow()
    {
...
...

Infine, utilizzando i due eventi di Loaded ed Unloaded della Window, aggiungiamo nei corrispondenti event handlers le istruzioni per poter inizializzare e deinizializzare l’instanza della Runtime.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    this.nui.Initialize(RuntimeOptions.UseColor | RuntimeOptions.UseDepth);
}

private void Window_Unloaded(object sender, RoutedEventArgs e)
{
    this.nui.Uninitialize();
}

Attraverso il metodo Initialize() vengono specificate quale features vogliamo utilizzare del Kinect (in questo caso la video camera ed il sensore di profondità). Il metodo Uninitialize()è invece necessario per rilasciare tutte le risorse unmanaged che il sistema ha allocato per l’utilizzo del Kinect.

Eseguendo questa banalissima applicazione, che non fa assolutamente nulla, noteremo solamente che all’esecuzione del metodo Initialize(), il dispositivo accenderà il trasmettitore ad infrarossi del sensore di profondità; già questo indica che l’inizializzazione è andata a buon fine. Viceversa, chiudendo la finestra dell’applicazione, il trasmettitore verrà spento (in quanto viene eseguito il metodo Uninitialize()); la deallocazione delle risorse è avvenuta correttamente.

StringBuilder : tre modi per ripulirla

Quando utilizziamo un oggetto StringBuilder e lo costruiamo in maniera incrementale attraverso i metodi Append() ed AppendFormat(), arriva talvolta il momento di doverlo ripulire per poterlo riutilizzare. Ovviamente, non ha senso istanziare un nuovo oggetto facendolo puntare dal medesimo riferimento che stavamo usando sino a quel momento e stranamente la classe StringBuilder non fornisce un metodo Clear().

Esistono, però, tre modi per ripulire un oggetto StringBuilder.

Impostare la lunghezza a zero

La property Length non è readonly, per cui è lecito eseguire la seguente operazione..

myStringBuilder.Length = 0;

Rimuoverne il contenuto

Utilizzare il metodo Remove() per tutta la lunghezza della stringa…

myStringBuilder.Remove(0, myStringBuilder.Length);

Replace del contenuto con la stringa vuota

Sostituire la stringa interna allo StringBuilder con una stringa “vuota”, utilizzando il metodoReplace()

string content = myStringBuilder.ToString();

myStringBuilder.Replace(content, string.Empty);

Pattern Singleton con parametri

Attraverso il pattern Singleton, siamo in grado di rendere disponibile un’unica istanza di una classe in un qualunque punto di un’applicazione essa venga referenziata, in quanto l’allocazione dell’oggetto avviene una sola volta in corrispondenza del primo accesso.

L’implementazione più semplice del pattern, senza considerare le problematiche di thread-safety (per le quali vi rimando a questo link), prevede all’interno della classe stessa :

  • un campo privato statico “instance” che rappresenterà l’unica istanza della classe a cui appartiene;
  • un costruttore privato, quindi non invocabile dall’esterno, che istanzia la classe assegnando il riferimento al campo suddetto;
  • un metodo o una proprietà che ritorna il riferimento al campo privato in maniera intelligente, creando l’istanza della classe se non esiste oppure ritornando il suo riferimento se la stessa è stata già allocata;

Poiché poche righe di codice riescono ad esprimersi più di mille parole, riporto di seguito la versione base di implementazione del pattern :

<br />public class MyClass<br />{<br />    private MyClass instance = null;<br />    ...<br />    ...<br />    private MyClass()<br />    {<br />        // costruzione oggetto<br />    }<br />    ...<br />    ...<br />    public MyClass Instance<br />    {<br />        get<br />        {<br />            if (instance == null)<br />                instance = new MyClass();<br />            return instance;<br />        }<br />    }<br />}<br />

Nel caso in cui abbiamo necessità di passare dei parametri in fase di allocazione dell’oggetto, come possiamo comportarci ?

Una possibilità potrebbe essere quella di implementare un metodo GetInstance() in luogo della property Instance e predisporlo con dei parametri in ingresso.

Ad esempio, nel caso di due parametri :

private MyClass(int param1, int param2)
{
    // costruzione oggetto
}

public MyClass GetInstance(int param1, int param2)
{
    if (instance == null)
        instance = new MyClass(param1, param2);
    return instance;
}

Con questa soluzione, si può dar luogo ad un’ambiguità. Basta considerare il seguente esempio :

MyClass a = MyClass.GetInstance(1,2);
MyClass b = MyClass.GetInstance(3,4);

Chi lavora con l’oggetto a, si aspetta di aver inizializzato correttamente l’oggetto con i valori 1 e 2. Chi lavora con l’oggetto b, invece, si aspetta di avere un oggetto il cui stato interno ha valori 3 e 4. Ovviamente, la prima inizializzazione prevarrà sulla successiva e quindi sia il riferimento a che b punteranno al medesimo oggetto avente stato con valori 1 e 2 (positivo per a ma non per b).

Una soluzione a questo problema può essere quello di implementare il pattern nel modo seguente :

public class MyClass
{
    private MyClass instance = null;
    ...
    ...
    private MyClass(int param1, int param2)
    {
        // costruzione oggetto
    }

    public MyClass GetInstance()
    {
        if (instance == null)
            throw new InvalidOperationException("MyClass instance not created !");
        return instance;
    }

    public static MyClass Create(int param1, int param2)
    {
        if (instance != null)
            throw new InvalidOperationException("MyClass instance already created !");

        instance = new MyClass(param1, param2);
        return instance;
    }
}

In pratica, viene fornito all’esterno un metodo statico di Create() per la prima allocazione dell’oggetto ed il metodo GetInstance() per ricavarne l’istanza corrente. Una doppia chiamate al metodo Create() solleverebbe l’eccezione “oggetto già creato” così come una chiamata alla GetInstance() senza aver prima creato l’oggetto, solleverebbe l’eccezione “oggetto non creato”.

XElement.Descendants() versus XElement.Elements()

Le classi XDocument e XElement forniscono due metodi che, a prima vista, sembrano dover fornire un risultato molto simile ma nei fatti così non è. I metodi in questione sono :

  • Descendants() : ritorna tutti i “discendenti” del document root/elemento oppure quelli con uno specifico nome (se passato come parametro);
  • Elements() : ritorna i “figli” diretti del document root/elemento oppure quelli con uno specifico nome (se passato come parametro);

Consideriamo la seguente stringa XML :

string xmls = @"
    <root>
        <node>
            <id>1</id>
        </node>
        <node>
            <id>2</id>
        </node>
        <node>
            <id>3</id>
            <innernode>
                <id>3.1</id>
            </innernode>
            <innernode>
                <id>3.2</id>
            </innernode>
        </node>
        <node>
            <id>4</id>
        </node>
    </root>";

ed il seguente frammento di codice che utilizza la classe XDocument.

XDocument xdoc = XDocument.Parse(xmls);

XElement[] app1 = xdoc.Root.Descendants().ToArray<XElement>();
XElement[] app2 = xdoc.Root.Elements().ToArray<XElement>();

In entrambi i casi facciamo ritornare un array con i nodi individuati dai due metodi.

Di seguito uno screenshot del loro contenuto

6646.XDocument_1_445068F5

Con il metodo Descendants() vengono recuperati tutti i discendenti a qualsiasi livello e quindi per esempio, considerando il nodo più complesso, anche il nodo con id = 3 (posizione 4 nell’array) ma successivamente tutti i suoi discendenti a tutti i livelli (dalla posizione 5 alla posizione 9 nell’array).

1538.XDocument_2_037E6CC6

Viceversa, con il metodo Elements() otteniamo solo i figli diretti del nodo root del documento. Per poter ricavare i loro corrispondenti ulteriori nodi figli, dovremo necessariamente eseguire un’ulteriore interrogazione su ciascuno di essi.

DebuggerDisplayAttribute : object inspection durante il debug

Durante una sessione di debug, Visual Studio ci offre la possibilità di ispezionare lo stato di un oggetto visualizzando i valori che assumono i campi e le proprietà dell’oggetto stesso. Quando una classe è molto complessa, però, è tedioso analizzarne singolarmente tutti i membri per valutare se lo stato dell’oggetto è coerente con le nostre attese. Visual Studio ci offre una feature che possiamo sfruttare per ovviare a questo inconveniente.

Comportamento base Object.ToString()

Consideriamo una banalissima classe Person …

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

… ed un’applicazione che istanzia un oggetto di questa classe e lo utilizza in qualche modo.

static void Main(string[] args)
{
    Person person = new Person() { FirstName = "Paolo", LastName = "Patierno" };
    ...
    ...
    ...
    Console.ReadLine();
}

Se eseguiamo l’applicazione in modalità debug e ci posizioniamo con il puntatore del mouse sull’oggetto person, Visual Studio visualizzerà la seguente informazione.

0508.dd_1_5DC33464

Esso rappresenta il nome della classe (nella forma <Namespace>.<Class>) di cui l’oggetto ne è un’istanza. Come si riesce ad ottenere questo comportamento ?

Ebbene, Visual Studio non fa nient’altro che eseguire il metodo ToString(), che come sappiamo fa parte della classe Object che è base di tutte le altre classi. Se una classe, non esegue l’override di questo metodo, il suo comportamento base è quello di visualizzare il nome della classe stessa.

Override del metodo ToString()

E’ possibile quindi sfruttare questa potenzialità eseguendo l’override del metodo ToString()nella classe Person per far visualizzare in debugging una sorta di summary informativo dello stato dell’oggetto.

public override string ToString()
{
    return String.Format("FirstName = {0}, LastName = {1}", this.FirstName, this.LastName);
}

Eseguendo nuovamente l’applicazione, l’output sulla variabile person sarà il seguente.

3683.dd_2_5C126890

In questo modo, abbiamo sott’occhio lo stato complessivo dell’oggetto (o almeno le informazioni che più ci interessano).

L’attributo DebuggerDisplay

Dal .Net Framework 3.5 è stato introdotto l’attributo DebuggerDisplayAttribute (nel namespace System.Diagnostics) da applicare ad una classe, che ci permette di ottenere il medesimo risultato senza la necessità di eseguire l’override del metodo ToString().

[DebuggerDisplay("FirstName = {FirstName}, LastName = {LastName}")]
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Esso prevede una stringa in ingresso che definisce la formattazione delle informazioni che vogliamo visualizzare (in maniera del tutto simile al String.Format() utilizzato precedentemente all’interno del metodo ToString()). Attraverso una sintassi del tipo{identificatore} è possibile accedere ad un campo, proprietà o addirittura un metodo della classe stessa.

Nel nostro caso, il risultato sarà del tutto analogo al precedente

2337.dd_2_507CAB51

Se avessimo addirittura un metodo che esegue una computazione e ritorna un valore, Visual Studio lo eseguirebbe in fase di debug visualizzandoci il risultato.

[DebuggerDisplay("FirstName = {FirstName}, LastName = {LastName}, Computation = {SomeMethod()}")]
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public override string ToString()
    {
        return String.Format("FirstName = {0}, LastName = {1}", this.FirstName, this.LastName);
    }

    public int SomeMethod()
    {
        int result;
        ...
        ...
        return result;
    }
}

Ottenendo il seguente risultato

0844.dd_3_020818E7

CLR – Thread Pool : elaborazioni periodiche con i timer

Capita spesso di dover eseguire periodicamente un’elaborazione ad intervalli prefissati. Una prima soluzione, assolutamente non efficace, potrebbe essere quella di predisporre un thread che al termine dell’elaborazione stessa va in sleep per un tempo pari al periodo richiesto, per poi rieseguire la computazione; tutto ciò in un opportuno ciclo. Considerando quanto detto una semplice idea e comunque un approccio non ottimale, la migliore soluzione la possiamo ottenere attraverso la classe Timer (del namespace System.Threading) che fa ampio uso del Thread Pool.

Considerando il costruttore più ampio di tale classe, esso prevede i seguenti parametri :

  • una delegate del tipo TimerCallback che rappresenta la callback da invocare ogni qual volta scatta il timer;
  • un oggetto con eventuali informazioni di stato da passare alla callback ogni qual volta viene invocata;
  • il dueTime, ossia il ritardo con il quale la callback viene invocata per la prima volta (specificando il valore 0, la callback viene invocata subito);
  • il periodo di invocazione della callback. Specificando Timeout.Infinite, viene disabilitata la periodicità e la callback sarà invocata solo una volta;

L’utilizzo di questa classe è strettamente legato al Thread Pool, in quanto il CLR utilizza uno dei worker thread del pool per invocare la callback associata al timer stesso. Ciò vuol dire che per più timer, impostati con periodi diversi e comunque aventi elaborazioni che non si sovrappongono nel tempo, il CLR è in grado di riciclare il medesimo thread per eseguire le corrispondenti callback con un notevole risparmio di risorse. E’ altresì ovvio che, qualora le elaborazioni delle callback si “sovrapponessero” nel tempo, il CLR sarebbe costretto ad allocare thread differenti per poterle eseguire.

Consideriamo la seguente semplice applicazione :

class Program
{

    // numero worker threads e completion port threads
    private static int workerThreads, completionPortThreads;

    private const int SCHEDULING_DELAY = 1000;    // delay avvio tra un thread e l'altro
    private const int NR_TIMER = 4;    // numero di thread
    private const long NR_DATA = 100000000;    // numero di elaborazioni

    private static Stopwatch sw;

    static void Main(string[] args)
    {
        Timer[] timers = new Timer[NR_TIMER];
        sw = new Stopwatch();

        WriteAvailableThreads();
        sw.Start();

        for (int idx = 0; idx < NR_TIMER; idx++)
        {
            timers[idx] = new Timer(TimerWork, idx, 5000, 1000);
            Thread.Sleep(SCHEDULING_DELAY);
        }
        Thread.Sleep(30000);

        for (int idx = 0; idx < NR_TIMER; idx++)
        {
            timers[idx].Dispose();
        }

        sw.Stop();
        Console.WriteLine("All works finished, elapsed time = {0} ms", sw.ElapsedMilliseconds);
        WriteAvailableThreads();

        Console.ReadLine();
    }

    private static void TimerWork(object state)
    {
        Console.WriteLine("Start TimerWork{0} at {1} ms with ThreadId {2}", state, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId);
        WriteAvailableThreads();

        long sum = 0;
        for (int i = 0; i < NR_DATA; i++)
        {
            sum += i;
        }

        Console.WriteLine("End TimerWork{0} at {1} ms with ThreadId {2}", state, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId);
    }

    private static void WriteAvailableThreads()
    {
        ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
        Console.WriteLine("workerthreads = {0}, completionPortThreads = {1}", workerThreads, completionPortThreads);
    }
}

Attraverso la quale creiamo NR_TIMER timer con un delay l’uno rispetto l’altro pari a SCHEDULING_DELAY e supponiamo che la callback associata ai timer esegua un’elaborazione (una banalissima somma) con un certo numero di dati pari a NR_DATA. Assumiamo che il dueTime sia 5000 ms e che il timer scatti una sola volta e non periodicamente.

Di seguito un primo risultato.

2376.timer1_34E65D33

SCHEDULING_DELAY = 1000, NR_TIMER = 4, NR_DATA = 10000000

Si osserva che schedulando abbastanza lentamente l’avvio dei timer, allo scattare delle relative callback e considerando la velocità dell’elaborazione, il CLR riesce a riciclare sempre il medesimo worker thread.

Proviamo a velocizzare la schedulazione dei timer.

4401.timer2_60B6B122

SCHEDULING_DELAY = 10, NR_TIMER = 4, NR_DATA = 10000000

In questo caso, il CLR è costretto ad utilizzare due worker thread per eseguire le callback previste per i quattro timer.

Aggiungendo la periodicità al timer (in questo caso l’ho settata a 1000 ms), la situazione diventa non molto predicibile e complessa. Dallo screenshot che segue si evince come ad un certo punto il CLR abbia avuto la necessità di allocare 6 worker thread (da 1023 disponibili a 1017) per gestire 4 timer. Ciò è dovuto al fatto che, quando un worker thread è occupato e non ce ne sono altri già creati e disponibili, il CLR è costretto ad allocarne uno nuovo e che quelli già presenti nel pool vengono eliminati, se non usati, solo dopo un certo tempo di inattività.

3443.timer3_58BF0EC0

SCHEDULING_DELAY = 1000, NR_TIMER = 4, NR_DATA = 10000000, PERIOD = 1000