Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - Magie con le stringhe

Guida al Visual Basic .NET

Capitolo 92° - Magie con le stringhe

<< Precedente Prossimo >>

Di tutti i tipi disponibili nel Framework .Net, sicuramente le stringhe costituiscono quello più potente, flessibile, versatile e utile. Saper lavorare cone le stringhe, massimizzare il programma, ridurre i tempi di elaborazione, produrre risultati migliori è davvero un'"arte", se così si può dire. Non sempre i programmatori scelgono la via più giusta: ecco una panoramica delle operazioni su stringa.

System.String

Il tipo che espone le stringhe è System.String ed essendo un tipo reference, dispone di tre costruttori in overload: il primo accetta un carattere e un intero X e genera di conseguenza una stringa formata da quel carattere ripetuto X volte; la seconda accetta un array di valori Char, che vengono convertiti in un unico dato string; la terza è un arricchimento della seconda che permette di specificare anche l'indice da cui partire e il numero di caratteri da prelevare. Non sempre il programmatore ne è a conoscenza, poichè la classica dichiarazione di una variabile di questo tipo è "Dim S As String", dalla quale non si evince con evidenza la natura di oggetto della stringa. Per essere precisi esse sono oggetti immutabili, checchè se ne pensi: infatti una volta assegnatovi un valore non c'è modo di modificarlo. Questo potrebbe sembrare strano, dato che le assegnazioni non hanno mai prodotto problemi di sorta, ma in realtà non lo è. La spiegazione è semplice: quando si assegna un valore a un oggetto stringa, si crea implicitamente un nuovo oggetto stringa e si passa il puntatore ad esso alla variabile in assegnazione:

'Oggetto stringa inizialmente uguale a Nothing
Dim S As String
'Crea un nuovo oggetto stringa "Ciao" e lo assegna a S
S = "Ciao" 

E lo stesso avviene quando si usa l'operatore di concatenazione &: tutti i valori stringa intercalati dall'operatore sono nuovi oggetti creati al momento dal programma. Per questo motivo l'approccio della creazione di stringhe con l'uso dell'operatore è sconsigliabile, a favore invece della funzione Format, come si vedrà da questo elenco di metodi e proprietà:

  • Chars(I) : collezione in sola lettura che contiene tutti i caratteri della stringa, accessibili tramite l'indice I. Chars è la proprietà di default della classe, quindi scrivere S.Chars(0) e S(0) è esattamente la stessa cosa
  • Clone : restituisce un nuovo oggetto il cui valore è identico alla stringa da cui viene chiamata la funzione
  • Compare : funzione statica con moltissimi overload che permette di confrontare due stringhe. Implementa IComparaer.Compare e restitusce valori definiti analizzati nei capitoli sulle interfacce
  • CompareTo : funzione che implementa l'interfaccia IComparable.CompareTo
  • Concat : concatena tutti gli argomenti passati
  • Contains(S) : restituisce True se S è contenuta nella stringa, altrimenti False
  • Copy(S) : come Clone, ma statica
  • Empty : costante che rappresenta una stringa vuota
  • EndsWith(S) : restituisce True se la stringa termina con S, altrimenti False
  • Equals(S) : determina se la stringa e S contengano lo stesso valore (è la stessa cosa che utilizzare l'operatore =)
  • Format(S, ...) : formatta la stringa secondo la stringa di formato S, utilizzando i parametri passati dopo
  • IndexOf(C) : restituisce l'indice di C nella stringa. -1 se la ricerca non ha prodotto risultati. Ha molti overload, che permettono di cercare una sottostringa e di specificare l'indice a cui iniziare la ricerca e la lunghezza del testo da controllare
  • IndexOfAny(C()) : restituisce l'indice di uno qualsiasi dei caratteri passati come array in C
  • Insert(S, I) : inserisce nella stringa un valore S all'indice I
  • IsNullOrEmpty : determina se la stringa sia Nothing oppure vuota
  • Join(S, V()) : concatena i valori nell'array V, separandoli con un separatore stringa S
  • LastIndex / LastIndexOfAny : come IndexOf, solo l'ultima occorrenza e non la prima
  • Length : la lunghezza della stringa
  • PadLeft / PadRight (C, N) : allinea a sinistra o a destra la stringa fino alla lunghezza N, riempiendo gli eventuali vuoti col carattere C
  • ReferenceEquals(S1, S2) : determina se S1 e S2 puntino allo stesso oggetto (è la stessa cosa che utilizzare l'operatore Is)
  • Remove(I, L) : rimuove dalla stringa tutti i caratteri a partire dall'indice I, opzionalmente specificandone anche il numero L
  • Replace(O, N) : rimpiazza tutte le occorrenze di O nella stringa con nuovi valori N
  • Split(C) : spezza la stringha in più sottostringhe utilizzando come separatore il carattere o l'array di caratteri passato
  • StartsWith(S) : determina se la stringa inizi per S
  • Substring(I, L) : ottiene una sottostringa ricavata prendendo solo i caratteri dall'indice I in poi, opzionalmente specificandone anche il numero L
  • ToCharArray : restituisce la stringa sotto forma di array di caratteri
  • ToLower : converte la stringa tutta in minuscolo
  • ToUpper : converte la stringa tutta in maiuscolo
  • Trim / TrimEnd / TrimStart : cancella tutti gli spazi bianchi dalla stringa. A seconda della funzione richiamata esegue tale processo sia all'inizio che alla fine, solo alla fine o solo all'inizio. L'unico overload di tutte queste funzioni permette di specificare una certa gamma di caratteri da eliminare in luogo degli spazi bianchi

Tutti i metodi non statici elencati, bisogna ricordarlo, sono funzioni d'istanza, quindi restituiscono un valore, ossia una stringa nuova ottenuta modificando quella esistente. Risulta infatti evidente che, siccome le stringhe sono oggetti immutabili, non possano essere modificate. Perciò il seguente codice non produrrà alcuna modificazione:

Dim S As String = "Ciao"
'Rimuove il primo carattere dalla stringa, ma il valore
'restituito viene perso in quanto non c'è 
'alcuna assegnazione
S.Remove(0, 1)
Console.WriteLine(S)
'> Ciao 

Mentre questo otterrà il risultato:

Dim S As String = "Ciao"
'Rimuove il primo carattere dalla stringa, e pone il
'riferimento alla nuova stringa creata in S
S = S.Remove(0, 1)
Console.WriteLine(S)
'> iao 

Nei frammenti di codice in cui si generano stringhe, tuttavia, è molto meglio utilizzare uno StringBuilder.

System.Text.StringBuilder

Questo oggetto ha il compito ci costruire pezzo per pezzo una stringa: è progettato in modo da lavorare con singoli caratteri alla volta, così da non creare alcun stringa temporanea in memoria, risparmiando molto spazio e molto tempo. Espone alcuni metodi presi da String, come Insert e Remove. Ha una proprietà caratteristica denominata Capacity, che indica il numero massimo di caratteri contenibili nell'oggetto, correlata di funzione EnsureCapacity che si assicura che questa condizione venga rispettata: durante il lavoro, se la lunghezza della stringa in elaborazione risulta maggiore della capacità, comunque, quest'ultima viene aumentata in modo da aderire alle nuove necessità di spazio, eliminando quindi ogni problema. Le procedure importanti sono Append, che accoda una stringa al tutto, AppendLine, che inoltre manda anche a capo e AppendFormat, che aggiunge un valore formattato con una stringa di formato, come al solito. Per provare l'efficacia di StringBuilder, ecco un test a prova di bomba che calcola le prestazioni di entrambi gli utilizzi:

Module Module1
    Sub Main()
        Dim T As New Stopwatch
        Dim S As String = ""
        Dim B As New StringBuilder

        'Cronometra quando si impiega a concatenare 100'000
        'caratteri con l'operatore &
        T.Start()
        For I As Int32 = 1 To 100000
            S &= "c"
        Next
        T.Stop()
        Console.WriteLine("Operatore &  : {0}ms", T.ElapsedMilliseconds)

        'Cronometra l'operazione con StringBuilder
        T.Reset()
        T.Start()
        For I As Int32 = 1 To 100000
            B.Append("c")
        Next
        T.Stop()
        Console.WriteLine("StringBuiler : {0}ms", T.ElapsedMilliseconds)

        Console.ReadKey()
    End Sub
End Module 

Sul mio computer, la prima versione impiega 14'678ms, mentre la seconda 4ms... Sembra sbalorditivo, ma StringBuilder, in questo caso è quasi 4000 volte più veloce. Guardando il consumo di RAM, si noterà inoltre che la prima versione spreca in media 2000 bytes in più di memoria.

System.Security.SecureString

Quando si memorizzano password o numeri importanti come quelli della carta di credito, le stringhe ordinarie perdono una quantità esagerata di punti in sicurezza. Infatti, oltre a essere reperibili nello spazio di memoria che il programma utilizza (anche se ottenere tali indirizzi è davvero assai difficile), spesso vengono salvate in file di sistema temporanei, più accessibili, oppure permangono in diverse copie nella memoria poichè, essendo immutabili, non se ne può veramente azzerare il valore. SecureString è un tipo sicuro che consente di evitare questi rischi: non presenta alcun costruttore, non è deducibile da una stringa ordinaria (altrimenti non avrebbe senso), costruisce la stringa sicura un carattere alla volta usando un algoritmo di criptazione per mascherare il carattere. Presenta metodi simili a StringBuilder, ma che lavorano solo con un carattere alla volta. Implementare una textbox che legga una password ad esempio, può essere un compito molto semplice: la stringa non viene memorizzata nel controllo, che espone un testo omogeneo, ma nell'oggetto SecureString: l'unico modo per costruire una stringa in questo modo è un carattere per volta utilizzando l'evento keypress.

Dim Password As New SecureString

Private Sub TextBox1_KeyPress(ByVal sender As Object, _ 
    ByVal e As KeyPressEventArgs) Handles TextBox1.Click
    Select Case Asc(e.KeyChar)
        Case 8
        'Il carattere 8 corrisponde al backspace: cancella 
        'l'ultimo carattere
        If TextBox1.SelectionStart > 0 Then
            Password.RemoveAt(TextBox1.SelectionStart - 1)
        End If
    '32 rappresenta uno spazio, ed è il primo carattere
    'intelleggibile nella tabella ASCII. In caso sia maggiore
    'o uguale a 32, quindi, il carattere non è di controllo
    Case Is >= 32
        'Se il cursore non si trova alla fine della stringa,
        'inserisce il carattere all'indice dato
        If TextBox1.SelectionStart < TextBox1.Text.Length - 1 Then
            Password.INSERT IGNOREAt(TextBox1.SelectionStart, e.KeyChar)
        Else
            Password.AppendChar(e.KeyChar)
        End If
        'Nella textbox, visualizza *
        e.KeyChar = "*"
    End Select
End Sub 

Un'alternativa a questo metodo non è data dall'uso della proprietà PasswordChar della textbox, poichè essa si limita a mascherare esteriormente il contenuto, che invece permane immutato nella memoria. L'unico modo per riprendere i dati così immessi nell'oggetto SecureString è utilizzare questo codice, che converte la password in un puntatore a stringa binaria e successivamente dereferenzia tale puntatore per ottenere il valore originario:

'Ottiene il puntatore a stringa BSTR. La sigla indica una Basic
'STRing alias Binary STRing. Le caratteristiche di questa
'stringa risiedono nelle modalità di memorizzazione
'sottoforma di dati nella memoria. È composta da un prefisso
'che ne specifica la lunghezza, un contenuto e un terminatore, 
'noto come NULL terminator ai programmatori C. 
'Non procedo oltre nell'argomento, poichè non è scopo 
'del capitolo trattare questo tipo di stringhe
Dim Ptr As IntPtr = Marshal.SecureStringToBSTR(Password)
'Dereferenzia il puntatore e ottiene la password vera
Dim Pass As String = Marshal.PtrToStringBSTR(Ptr)

'In questa parte viene usato il valore della stringa

'Quindi il suo spazio di memoria viene istantaneamente liberato per 
'evitare che possa essere rintracciata in qualche modo
Marshal.ZeroFreeBSTR(Ptr) 

Marshal è una classe appartenente al Namespace System.Runtime.InteropServices e serve per copiare e manipolare blocchi di memoria a livello molto basso, senza riguardo nè per il tipo che per la compatbilità, ma solo per indirizzi e dimensioni. È il wrapper managed del corrispondente metodo unmanaged CopyMemory della libreria kernel32.dll di Windows. Neppure questo argomento verrà trattato oltre in questo capitolo, ma vi rimando alla lezione corrispondente sul pinvoke.

<< Precedente Prossimo >>
A proposito dell'autore

C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...