Timer e Backgroud worker

In VB.NET e in C# esistono metodi che permettono la manipolazione di eventi da diversi punti di vista. In questo articolo ne vedremo, nello specifico, tre:

  • Timer: permette la creazione di un timer con cui gestire l’attivazione di determinate azioni;
  • Background worker: consente di dedicare un thread all’esecuzione di alcune specifiche operazioni;
  • Extention method: metodi aggiunti a degli oggetti dopo la creazione originaria di essi.

Più nel dettaglio, gli elementi precedente elencati hanno le seguenti caratteristiche:

La classe timer consente l’implementazione di un timer che può avere molteplici utilizzi. In maniera “passiva” può essere utilizzato per controllare l’effettivo tempo di esecuzione di una operazioni o di un intero programma. In questo modo diventa possibile il confronto del tempo impiegato, ad esempio, da algoritmi diversi che producono lo stesso risultato, permettendo di scegliere il più efficiente (in termini di tempo richiesto, ovviamente). In maniera “attiva” un timer può essere introdotto per la gestione di eventi a intervallo in un’applicazione. Questo fornisce molteplici vantaggi, permettendo di attivare una determinata routine senza la necessità che l’utente la richiami direttamente, ma associandola, ad esempio, ad un determinato lasso temporale.

Il background worker consente di eseguire un’operazione su un thread separato. Questo può rivelarsi essere molto utile, in quanto consente al programma di continuare l’esecuzione, senza dover necessariamente aspettare che operazioni molto lente (il cui risultato potrebbero essere necessario solo ad un punto più avanzato del programma) blocchino l’intera esecuzione. Si può applicare, ad esempio, ad operazioni come download e transazioni di database. In sostanza, l’interfaccia utente ed il programma rimangono attivi, nonostante siano in esecuzione delle operazioni (cosa che solitamente implica la disattivazione di questi ultimi). È possibile creare il BackgroundWorker in due diversemaniere: (i) a livello di codice; (ii) trascinandolo sul form dalla scheda componenti della casella degli strumenti. Se lo si crea nella progettazione Windows Form, questo verrà visualizzato nella barra dei componenti e le relative proprietà verranno visualizzate nella Finestra Proprietà, semplificandone la gestione.

Gli extension method (metodi di estensione) sono metodi che consentono di aggiungere funzionalità ad un oggetto una volta che esso è stato creato, senza la necessità di creare una nuova classe che implementi tutte le caratteristiche richieste. I metodi di estensione permetto la scrittura di un metodo la cui chiamata sarà identica alla chiamata di un metodo di istanza del tipo esistente. Può essere solo una routine Sub o una procedura Function. Tutti i metodi di estensione vengono indicati dalla dichiarazione Extension durante la creazione degli stessi e devono essere definiti in modulo.

Ricorsione e iterazione

I metodi ricorsivi ed i metodi iterativi sono di per sé simili, in quanto utilizzano entrambi utilizzo una struttura ripetitiva, caratterizzata da una condizione di uscita che termina l’esecuzione. La principale e fondamentale differenza, tuttavia, risiede nel fatto che un algoritmo iterativo presenta una struttura, quale un loop for, while o do-while, dove un’operazione viene eseguita e conclusa finché la condizione di uscita viene raggiunta. Un algoritmo ricorsivo, invece, richiama se stesso fino al raggiungimento della condizione di uscita, generando uno stack di chiamate che verrà chiuso a cascata, a partire dall’ultima chiamata generata, solo una volta che sarà raggiunta la condizione di uscita.

In generale, un algoritmo iterativo risulta spesso più leggibile, più facile da implementare e tendenzialmente meno incline a generare errori (anche grazie al meno peso che comporta sulla memoria). Di contro, però, gli algoritmi ricorsivi sono particolarmente adatti per risolvere determinati problemi, fornendo soluzioni semplici e facilmente implementabili, laddove altri algoritmi risultano eccessivamente complicati o addirittura impossibili da implementare.

Un esempio di algoritmo iterativo è il bubble sort (quasi tutti i sort sono algoritmi iterativi), mentre un esempio di algoritmo ricorsivo è il merge sort.

Uso della memoria da parte di un programma

Quando un programma viene avviato, viene messa a disposizione, da parte del computer, una certa quantità di memoria. Questa viene allocata principalmente per 4 diverse componenti:

  • una prima area di memoria è dedicata a contenere le istruzioni stesse del programma;
  • una seconda area di memoria è dedicata alle variabili che non sono definite nei metodi, ma che appartengono alla classe intera e servono generalmente per l’esecuzione del programma, le cosiddette variabili globali;
  • un’area di memoria, denominata heap, viene dedicata a tutti gli oggetti che non occupano una quantità di memoria definita, ma invece necessitano di una quantità di memoria dinamica, come ad esempio i reference type;
  • un’ultima area di memoria, denominata stack, viene dedicata alle informazioni relative alle chiamate e ai value type che non ricadono nelle variabili globali, ovvero quelle variabili proprie dei metodi (e non genericamente della classe stessa) che necessitano di una quantità ben definita di memoria.

Vedendo nel dettaglio l’area di memoria stack, essa è composta da diversi “settori” denominati stack frame. Ad ogni chiamata di un metodo viene istanziato uno stack frame dedicato, dove vengono registrate tutte le informazioni:

  • variabili value type istanziate in quel metodo;
  • parametri utilizzati;
  • indirizzo di ritorno al metodo chiamante.

La gestione degli eventi nella memoria stack, ovvero dell’aggiunta e dell’eliminazione degli stack frame, avviene secondo lo schema “last in, first out” che sostanzialmente consiste nell’esecuzione degli stack frame partendo dalla chiamata più recente per tornare poi a ritroso al primo metodo chiamato (solitamente il main), in maniera uguale e contraria a come avvengono le chiamate nell’esecuzione effettiva del programma.

Se un programma eccede la dimensione massima consentita (che è pari a 1 GB nei programmi in windows a 32 bit e a 4 GB nei programmi in windows a 64 bit), ad esempio quando viene chiamato un programma ricorsiva un numero eccessivo di volte, viene prodotta una StackOverflowException ed il programma viene, di conseguenza, interrotto.

Trasformazione di coordinate: dal “mondo reale” al “mondo virtuale”

Uno degli aspetti fondamentali quando si disegna un grafico attraverso C# o VB.NET è il riuscire a trasformare in maniera opportuna le coordinate del dataset che si vuole rappresentare. Infatti, una trasformazione delle coordinate errata può portare dei risultati sbagliati e fuorvianti. La prima cosa che bisogna ricordare è che il sistema richiede le coordinate dei punti in forma di pixel. In particolare, se si vuole riprodurre un certo punto determinato dal valore di due coordinate X e Y sullo schermo, bisognerà ottenere il suo equivalente grafico, dato dalle trasformazioni delle due variabili in coordinate pixel e con riferimento l’origine della bitmap istanziata in precedenza.

Questa trasformazione è di per sé molto semplice, ma richiede molta attenzione in quanto fondamentale per una giusta rappresentazione. Supponendo di avere un dataset in due variabili, X e Y, e supponendo di aver creato una bitmap caratterizzate dalle seguente variabili:

  • Left, ovvero la coordinata X del vertice in alto a sinistra del rettangolo;
  • Top, ovvero la coordinata Y del vertice in alto a sinistra del rettangolo;
  • Width, ovvero l’ampiezza del rettangolo;
  • Height, ovvero l’altezza del rettangolo;

allora occorrerà ottenere una coppia di coordinate, \hat{X} e \hat{Y}, che facciano rifermento alla bitmap, in particolare all’estremo sinistro, di cui possediamo le coordinate. Per ogni punto basterà, quindi, applicare queste trasformazioni:

La X può essere trasformata in maniera tale che ricada all’interno della bitmap. Servirà quindi che il massimo valore delle X sia associato all’estremo destro della bitmap e il minimo all’estremo sinistro, scalando di conseguenza tutti gli altri valori in maniera che risultino compresi tra questi due. Questo è ottenibile, per una generica osservazione i-esima, attraverso la seguente formula:

\hat{x}_i = \mathrm{Left} + \mathrm{Width} \cdot \frac{x_i - min(X)}{max(X)-min(X)}

Analogamente, bisognerà scalare in modo uguale anche le coordinate della Y, ricordando però che la bitmap fornisce un riferimento per l’estremo sinistro superiore, e non, come convenzionalmente è abitudine nel definire l’origine di un grafico, l’estremo sinistro inferiore. La formula risulterà essere la seguente:

\hat{y}_i = \mathrm{Top} +\mathrm{Height} \cdot( 1 - \frac{y_i - min(Y)}{max(Y)-min(Y)})

Principali oggetti grafici

Uno degli aspetti dove Visual Studio eccelle è nella gestione dell’interfaccia grafica. Infatti, sia VB.NET che C# mettono a disposizione una libreria, chiamata “GDI+“, che permette la rappresentazione di grafici (utile sopratutto per rappresentazioni statistiche), fornendo la possibilità di gestire diverse opzioni grafiche con molta facilità. La creazione di un grafico segue sostanzialmente alcuni step base, ovvero:

  1. creazione del bitmap, ovvero un’area di schermo (e conseguentemente di memoria, poiché verrà dapprima salvata e solo in seguito istanziata ) rettangolare caratterizzata da due variabili, altezza (height) e larghezza (width), che servirà per mostrare l’immagine successivamente creata;
  2. istanziazione dell’oggetto grafico graphics, ovvero l’istanziazione di GDI+ che permetterà in seguito di gestire e di creare il grafico stesso;
  3. esecuzione dei comandi per la creazione della parte grafica;
  4. display bitmap, ovvero rappresentazione del bitmap con un oggetto e stampa a schermo.

Il terzo step è quello che definisce il grafico in sé, andando a creare assi e oggetti che verranno poi disegnati. Bisogna perciò fare molta attenzione durante questo passaggio, in quanto un errore potrebbe comportare la creazione di un grafico completamente errato. Per la creazione del grafico esistono diversi oggetti che possono essere utilizzati, tra i quali:

  • Pen: oggetto che permette la creazione di linee, può essere manipolato con diverse opzioni grafiche quali:
    • Width, ovvero ampiezza del tratto;
    • Color, ovvero colore del tratto;
    • DashStyle, ovvero stile del tratto.
  • Brush: permette di riempire aree del grafico (la sua traduzione, di fatti, è pennello);
  • Point: permette la creazione di punti, determinati da due coordinate;
  • Rectangle: struttura “value type” che consente di disegnare un rettangolo attraverso 4 argomenti:
    • X, ovvero la coordinata X del vertice in alto a sinistra del rettangolo;
    • Y, ovvero la coordinata Y del vertice in alto a sinistra del rettangolo;
    • Width, ovvero l’ampiezza del rettangolo;
    • Height, ovvero l’altezza del rettangolo.
  • Font: consente di aggiungere un piccolo testo alle label del grafico e definisce uno specifico formato di testo;
  • Clear: metodo che serve per pulire l’intera area grafica, cancellando ogni istanza grafica.

Lambda expressions

In programmazione informatica, una funzione anonima o funzione lambda è una funzione o una subroutine definita, e possibilmente chiamata, senza essere legata ad un identificatore (da qui, appunto, funzione anonima ). Le funzioni lambda sono una forma di funzione nidificata, che consente l’accesso alle variabili nella portata della funzione contenitrice (variabili non locali).

Forniscono un scorciatoia rapida per applicare funzioni senza necessità di definire un nuovo metodo, o addirittura una nuova classe. Tuttavia, se un’espressione viene utilizzata in maniera ricorrente, è comunque preferibile costruire un metodo che possa essere chiamato ogni volta, senza dover ripete, ogni volta che risulta necessario, la definizione della funzione lambda.

Possono essere facilmente implementate sia in C#, sia in VB.NET. Vengono utilizzate spesso per la creazione di nuovi oggetti, ad esempio durante la gestione di un database, sopratutto se non relazionale, ma possono essere utilizzati anche per semplici trasformazioni. Alcuni esempi di questi utilizzi nei due linguaggi precedentemente citati sono i seguenti:

C#

// definizione di una lista di oggetti
List<Customer> custList = new List<Customer>
    {new Customer()
          { CustomerId = 1,
            FirstName="Bilbo",
            LastName = "Baggins",
            EmailAddress = "bb@hob.me"},
    new Customer()
          { CustomerId = 2,
            FirstName="Frodo",
            LastName = "Baggins",
            EmailAddress = "fb@hob.me"},
    new Customer()
          { CustomerId = 3,
            FirstName="Samwise",
            LastName = "Gamgee",
            EmailAddress = "sg@hob.me"},
    new Customer()
          { CustomerId = 4,
            FirstName="Rosie",
            LastName = "Cotton",
            EmailAddress = "rc@hob.me"}};
// definizione di una espressione lambda che opera sulla lista
var foundCustomer = custList.FirstOrDefault(c =>
                        c.CustomerId == 4);


// definizione di una espressione lambda che applica una funzione ad un oggetto (in questo caso la somma di due oggetti)

int add = (x, y) => { return x + y };
Console.WriteLine("{0}",add(1,2));

VB.NET

// definizione di una lista di oggetti
Dim custList As List(Of Customer) = New List(Of Customer) From {
    New Customer() With {
        .CustomerId = 1,
        .FirstName = "Bilbo",
        .LastName = "Baggins",
        .EmailAddress = "bb@hob.me"
    },
    New Customer() With {
        .CustomerId = 2,
        .FirstName = "Frodo",
        .LastName = "Baggins",
        .EmailAddress = "fb@hob.me"
    },
    New Customer() With {
        .CustomerId = 3,
        .FirstName = "Samwise",
        .LastName = "Gamgee",
        .EmailAddress = "sg@hob.me"
    },
    New Customer() With {
        .CustomerId = 4,
        .FirstName = "Rosie",
        .LastName = "Cotton",
        .EmailAddress = "rc@hob.me"
    }
}
// definizione di una espressione lambda che opera sulla lista
Dim foundCustomer = custList.FirstOrDefault(Function(c) c.CustomerId = 4)


// definizione di una espressione lambda che applica una funzione ad un oggetto (in questo caso la somma di due oggetti)

Dim add = Function(x, y)
             Return x + y
          End Function

Metodi disponibili per leggere file testuali di dati statistici, con esempi

In C# e VB.NET esistono diversi metodi per leggere i file di testo, da cui è possibile ottenere gli stessi risultati attraverso specificazioni diverse. Rimane discrezione del programmatore decidere quale sia il metodo più adatto nelle diverse situazioni. I tre metodi principali sono:

  • Hardcode;
  • StreamerReader;
  • TextFieldParse

Il metodo hardcode è sicuramente quello che presenta la sintassi più semplice tra tutti. Tuttavia, questa semplicità viene guadagnata a scapito di una perdita di ottimalità e precisione, nonché di leggibilità stessa. Il metodo hardcode consiste, sostanzialmente, nel definire una nuova variabile come una stringa che si inizializza specificando il percorso del file in questione (ovviamente tra apici). Di seguito sono riportati un esempio in VB.NET:

VB.NET

Dim File As String = "C://File.csv"

Il metodo StreamerReader risulta decisamente più comodo e versatile rispetto al precedente. Infatti, fornisce sia la possibilità di caricare un file direttamente da sistema, attraverso una “open file dialog” e quindi non rende necessario modificare ogni volta direttamente il codice nel caso si volesse utilizzare lo stesso programma per un set di dati diverso, sia la possibilità di caricare il file in maniera hardcoded, ovvero definendolo direttamente all’interno del codice girato e semplificato quindi, ad esempio, un’esecuzione ripetuta di un programma utilizzato su uno specifico dataset. Un esempio della versione che utilizza la versione dinamica in VB.NET e C# è la seguente:

VB.NET

'ottenimento della path del file attraverso una Open File Dialog
Dim ofd As New OpenFileDialog
ofd.ShowDialog()
 
'istanziamento della path in un oggetto
Dim sr As New System.IO.StreamReader(ofd.FileName)
 
'lettura delle righe del file e estrazione dei dati
Dim sr As New StreamReader(ofd.FileName)
Dim RigaH As String = sr.ReadLine()   'in questo modo legge l'header
While Not sr.EndOfStream
 Dim RigaDati As String = sr.ReadLine()
 Dim Dati() As String = RigaDati.Split(";".ToCharArray,StringSplitOptions.RemoveEmptyEntries)
End While

C#

            using (StreamReader sr = new StreamReader("TestFile.txt"))
            {
                string line;
                // Read and display lines from the file until the end of
                // the file is reached.
                while ((line = sr.ReadLine()) != null)
                {
                    Console.WriteLine(line);
                }
            }

L’ultimo metodo considerato è il TextFieldParse. Nonostante questo metodo risulti essere meno efficiente rispetto al StreamerReader, viene comunque spesso utilizzato per la lettura di file con estensione .csv. La principale differenza rispetto al metodo precedente sta nel fatto che, in VB.NET, questo metodo risulta già definito e non è quindi richiesta alcuna reference. Inoltre la sintassi rimane pressoché invariata, con l’eccezione dell’inizializzazione del percorso. Infatti, avviene una sostituzione del comando split e una eliminazione (o sostituzione) dei delimitatori. Un esempio di codice di questo metodo in VB.NET è il seguente:

VB.NET

Using MyReader As New Microsoft.VisualBasic.FileIO.
    TextFieldParser("c:\logs\bigfile")

    MyReader.TextFieldType = 
        Microsoft.VisualBasic.FileIO.FieldType.Delimited
    MyReader.Delimiters = New String() {vbTab}
    Dim currentRow As String()
    'Loop through all of the fields in the file. 
    'If any lines are corrupt, report an error and continue parsing. 
    While Not MyReader.EndOfData
        Try
            currentRow = MyReader.ReadFields()
            ' Include code here to handle the row.
        Catch ex As Microsoft.VisualBasic.FileIO.MalformedLineException
            MsgBox("Line " & ex.Message & 
            " is invalid.  Skipping")
        End Try
    End While
End Using

Elementi fondamentali della specifica CSV

In programmazione ed in statistica è fondamentale saper lavorare con dataset esterni. La prima cosa necessaria è saper importare correttamente diversi tipi di file all’interno del sistema in cui si sta lavorando. La maggior parte dei file contenenti dataset viene salvato attraverso l’estensione .csv.

Il CSV, abbreviazione di comma-separated values, è un formato di file basato su file di testo utilizzato per l’importazione ed esportazione (ad esempio da fogli elettronici o database) di una tabella di dati. Non esiste uno standard formale che lo definisca, ma solo alcune prassi più o meno consolidate. Ogni riga della tabella è normalmente rappresentata da una linea di testo, composta da diverse componenti (campi, o colonne, se si fa riferimento alla costruzione matriciale di un dataset). La separazione delle componenti è uno degli aspetti cruciali della lettura di un file .csv. Non esiste, infatti, una vera convenzione su quale separatore indichi la fine di una componente e l’inizio della successiva, ed è essenziale riuscire a ottenere questa informazione. I separatori più utilizzati sono “,” e “;”, ma è possibile trovare anche file che utilizzano come separatori spazi, virgolette o apici stessi. Questo implica che solitamente non è consigliato l’utilizzo di un di questi caratteri nella creazione dei record di un dataset, in quanto può facilmente portare ad una divisione errata del dataset.

Algoritmi classici di manipolazione dei bit

Gli algoritmi di manipolazione dei bits permettono, in maniera elegante ed efficacie, di risolvere problemi come il trovare l’ultima cifra significativa di un numero rappresentato in binario (LSB, dall’inglese Least Significant Bit), oppure il contare il numero di setbit (ovvero bit pari a 1) in un numero qualsiasi codificato in binario. Queste domande sono spesso poste durante colloqui di lavoro presso grandi società, come Google e Amazon.

Determinare il LSB di N

Valutiamo ora il problema del trovare l’ultima cifra significativa di un numero N rappresentato in binario. Per farlo, si utilizza l’operatore logico And in VB.NET, equivalente in C# all’operatore &, eseguendo l’operazione N And 1. In questo modo il programma valuterà solo l’ultimo bit del numero N e restituirà 1 se è pari a 1, 0 altrimenti. In questo modo, è possibile agilmente creare un programma per controllare se un numero risulta essere dispari o meno (poichè, se dispari, terminerà con un 1 nella sua codifica in binario).

C#

public bool isOdd(int n)
{
    if ((n & 1) == 1)
        return true;
    else
        return false;
}

VB.NET

Public Function IsOdd(ByVal N As Integer) As Boolean
    If (N And 1) = 1 Then
        Return True
    Else
        Return False
    End If
End Function

Contare il numero di setbit in N

Algoritmo Naive

Un altro domanda classicamente posta durante i colloqui di lavoro, consiste nel determinare il numero di bit pari ad 1 nella codifica binaria di un dato numero N. Anche questo quesito può essere svolto elegantemente. Si può costruire un algoritmo Naive andando a complicare leggermente la soluzione del quesito precedente. Abbiamo infatti visto come è possibile valutare l’ultimo bit di un numero N codificato in binario. A questo punto, tramite l’operatore di scorrimento shift, identificato in C# e in VB.NET con il simbolo “>>“, è possibile traslare di una posizione verso destra i bit che compongono la sequenza della rappresentazione binaria di N. Si procederà quindi a valutare iterativamente l’ultimo bit significativo, fino a che il numero di partenza non sarà pari a 0. Graficamente, può essere rappresentato come segue. Prendiamo in considerazione il numero 25 e la sua rappresentazione binaria:

Step 1: N11001N&1 = 1
Step 2: N>>11100N&1 = 0
Step 3: N>>1110N&1 = 0
Step 4: N>>111N&1 = 1
Step 5: N>>11N&1 = 1
FineTot = 3

Una semplice implementazione di questo algoritmo in C# e VB.NET è la seguente:

C#

public int countSetBitNaive(int n)
{
    int sumSetBit = 0;
    while (n != 0)
    {
        sumSetBit += n & 1;
        n >>= 1;
    }
    return sumSetBit;
}

VB.NET

Function CountSetBitNaive(N As Integer) As Integer
    Dim SumSetBit As Integer = 0
    Do While N <> 0
        SumSetBit += N And 1 
        N >>= 1 
    Loop
    Return SumSetBit
End Function

Algoritmo di Kernighan

Un altro modo per implementare questo algoritmo, velocizzando il procedimento ad un numero di passaggi pari all’effettivo numero di bit pari ad 1, è attraverso l’algoritmo di Kernighan. Questo algoritmo sfrutta la relazione che esiste tra un numero e il precedente, ovvero tra N e (N-1), quando sono codificati in binario. Se infatti si applica l’operazione And su questi due numeri, si ottiene l’azzeramento del primo bit di N a partire da destra che risulta essere pari a 1. In questo modo è possibile fare un loop in cui viene contato semplicemente il numero di volte in cui l’operazione viene eseguita prima che il numero diventi pari a 0. Un’implementazione di questo algoritmo è la seguente:

C#

public int countSetBitKernighan(int n)
{
    int sumSetBit = 0;
    while (n != 0)
    {
        n = n & n-1;
        sumSetBit +=  1;
    }
    return sumSetBit;
}

VB.NET

Function CountSetBitKernighan(N As Integer) As Integer
    Dim SumSetBit As Integer = 0
    Do While N <> 0
        N = N AND N - 1
        SumSetBit +=  1 
    Loop
    Return SumSetBit
End Function

Testare se N è una potenza di 2

Un altro quesito facilmente risolvibile attraverso gli operatori logici quando, N è codificato in binario, è il controllare se N risulta essere una potenza di 2 o meno. Questo può essere rapidamente risolto ricordando che se N è una potenza di 2, la sua codifica in binario sarà uguale ad un primo bit pari a 1 seguito da tanti bit pari a 0 quanto è la potenza di 2. Ad esempio, 16, che è pari a 2^4, sarà rappresentato in binario dal numero 1 0 0 0 0. Per verificare allora se un numero è una potenza di 2 basterà allora verificare che N And (N-1) restituisce subito come risultato 0. Un’implementazione di questo algoritmo in C# e VB.NET è la seguente:

C#

public bool isPowerOf2(int n)
{
    if ((n & n - 1) == 0)
        return true;
    else
        return false;
}

VB.NET

Public Function IsPowerOf2(ByVal N As Integer) As Boolean
    If (N And N - 1) = 0 Then
        Return True
    Else
        Return False
    End If
End Function

Determinare la massima potenza di 2 che divide N

Un ultimo quesito risolto in questo articolo riguarda il trovare la massima potenza di 2 che divide un numero N. Considerando la sua scrittura in binario, è facile notare che essa è esattamente pari al primo bit (partendo da destra) pari ad 1. Basterà, quindi, porre pari a 0 tutti i bit che seguono il primo bit pari ad 1. Per farlo non servirà altro che eseguire l’operazione N And Not (N – 1). Una semplice implementazione di questo algoritmo è la seguente:

C#

public int maxPowerOf2_factor(int n)
{
    return (n & !(n - 1));
}


VB.NET

Public Function MaxPowerOf2_factor(ByVal N As Integer) As Integer
    
    Return (N And Not (N - 1))

End Function

I principali operatori in VB.NET e C#

In informatica e programmazione, un operatore è un simbolo che specifica quale legge applicare a uno o più operandi, per generare un risultato. Gli operatori possono essere classificati in base al numero di operandi che accettano, ovvero in base al numero di dati su cui lavorano, o in base alle operazioni che svolgono e ad i risultati che forniscono. La prima divisione degli operatori presenti in C# e VB.NET in base al numero di operandi che accettano è la seguente:

  • operatori unari (o unary) sono operatori che richiedono un solo operando e sono ad esempio l’operatore not in VB.NET e !in C#;
  • operatori binari (o binary) lavorano su due operandi, come ad esempio gli operatori aritmetici più comuni o quelli di assegnamento;
  • operatori ternari (o ternary) lavorano su tre operandi; sono operatori non molto comuni, uno dei rari esempi è l’operatore condizionale, usato in programmazione, rappresentato in C# attraverso il simbolo “?” e in VB.NET attraverso la funzione If(). Il suo funzionamento è relativamente semplice. In C# la sintassi A?B:C significa che se A risulta essere vero allora il programma restituisce B, altrimenti viene restituito C. Analogamente, questa operazione può essere compiute in VB.NET attraverso la sintassi If(A,B,C).

Una suddivisione per funzionalità degli operatori è la seguente:

  • aritmetici: sono +-*/%\ e lavorano su due operandi;
  • incremento e decremento: sono unari, rispettivamente aumentano o diminuiscono di uno l’unico operando che prendono;
  • logici booleni:  lavorano su dati booleani, ovvero variabili che possono assumere solo i valori vero e falso e servono a formulare condizioni composte; ne sono un esempio in NOTANDORXOR VB.NET, o gli equivalenti in C# !&|^;
  • bitwise: consentono di manipolare direttamente i bit di un valore e sono uguali agli operatori logici, fatta eccezione per !, che viene tradotto con ~;
  • relazione: sono operatori binary che consentono di controllare la relazione tra due operandi, sono <>=<><= , >=isisnot in VB.NET e C#, fatta eccezione per i primi due dove in C# sono tradotti con !===;
  • concatenazione di stringhe: rispettivamente &+ in VB.NET e C#, servono per la concatenazione di stringhe;
  • condizionali In VB.NET si utilizzano ANDALSOORELSE, in C# sono tradotti con &&||. Questi operatori differiscono dai logici perché quest’ultimi valutano i due termini operandi in ogni caso, mentre gli operatori condizionali valutano il secondo operando solo se strettamente necessario. Questo procedimento è detto short circuiting.