Guarda! Senza mani! - Seconda parte
a cura di Sabrina Cosolo e Diego Cattaruzza (requisiti: conoscenza intermedia di Sql Server e di .Net)Nota: ogni articolo sarà corredato del codice sviluppato fino a quel punto, comprendendo quindi anche il codice sviluppato negli articoli precedenti, ma gli articoli saranno più utili se verranno seguiti nello svolgersi. In altre parole, non scaricate il sorgente prima di aver finito di seguire l'articolo. Poi, il sorgente dovrebbe fungere solo da verifica.
Configurare una applicazione
Usualmente, la parte relativa alla configurazione delle impostazioni di una applicazione viene lasciata sempre per ultima, per Sabrina invece costituisce il primo argomento, perché fa parte delle cose che definiscono strutturalmente una applicazione e pensarci solo dopo che è stata costruita potrebbe portare a dover riscrivere codice e magari (così) introdurre bug indesiderati.Nella maggior parte dei casi, una applicazione ben fatta, di qualsiasi tipo, ha bisogno, quando viene installata su un PC (che non sia quello del programmatore), di alcuni dati parametrici esterni. Alcuni si possono leggere dalle variabili di sistema utilizzando la classe System.Environment; altri possono essere inseriti nei file di risorse, ad esempio le immagini e le stringhe di messaggio (che in questo modo possono essere generate in più lingue); altri ancora sono parametri che dipendono dalla configurazione della macchina su cui il programma è installato, quindi sono parametri di applicazione; altri ancora dipendono dall'utente loggato, ad esempio la modalità e i parametri di connessione al server dati.
Il Framework .NET fornisce una serie di classi che permettono di gestire i parametri di applicazione e di utente (su questo sito trovate un bell'articolo di Rudy Azzan che ne parla estesamente), noi però preferiamo non utilizzarle per vari motivi che non vi elencheremo qui, ma soprattutto per il fine didattico di insegnarvi quanto .NET renda facile questo tipo di lavoro.Facciamo la lista della spesa e vediamo che cosa ci serve memorizzare nei nostri Setting.
Per farlo, vediamo come sono fatte le ConnectionString per SQL Server 2005:
Connessione Trusted "Data Source=MyComputer; Initial Catalog=MyDatabase; Integrated Security=True" Connessione Sql "Data Source=MyComputer; Initial Catalog=MyDatabase; Persist Security Info=True; User ID=MyUsername;Password=MyPassword" Se analizziamo gli elementi che compongono le stringhe di connessione, possiamo dire che una connessione di tipo Trusted (ovvero una connessione dove l'utente di Windows è mappato in SQL Server con i permessi necessari alle sue attività sul database server) contiene i seguenti parametri:
- Nome del Server Sql (che per l'instanza standard di SQL Server corrisponde al nome del PC, mentre per SQL Express potrebbe essere diversa quindi controllate).
- Nome del database a cui connettersi
- Indicatore che la sicurezza integrata è attiva.
Per la connessione di tipo SQL (ovvero una connessione ove ogni utente ha il suo Username e la sua Password servono i seguenti parametri:
- Nome del Server Sql
- Nome del database a cui connettersi
- Username
- Password
Possiamo quindi affermare che ci sono due elementi uguali per entrambe, Nome del server e Nome del database, mentre gli altri tre elementi possono dipendere da chi è l'utente che si connette. Pertanto abbiamo individuato setting di tipo applicativo e setting di tipo user. Però tutti questi setting hanno qualcos'altro in comune: sono fatti tutti allo stesso modo, ovvero sono composti da un Nome e da un Valore.
Le nostre ConnectionString potranno divenire: Connessione Trusted "Data Source={0}; Initial Catalog={1}; Integrated Security=True" Connessione Sql "Data Source={0}; Initial Catalog={1}; Persist Security Info=True; User ID={2};Password={3}" Mentre i nostri setting ad esse relativi diverranno: Server MyServer Database MyDatabase Trusted Yes/No Username MyUser Password MyPassword Pertanto, la rappresentazione di un setting potrebbe essere una classe serializzabile con due variabili member private esposte tramite due proprietà pubbliche.
Visto che un setting rappresenta un oggetto ben definito, potremo chiamarlo, usando un termine che speriamo diventi familiare a tutti voi, un'Entità (Entity). Il concetto di Entità è estesamente spiegato da molti esperti e vi invitiamo ad una ricerca su Google per trovare materiale completo e interessante in merito.
Per l'uso che ne faremo all'interno di questa serie di articoli, l'Entità rappresenta un singolo oggetto contenente dati, che ha un uso e uno scopo ben definito e tutte le funzionalità che permettono generarlo e manipolarlo. Un'entità può essere di tipo semplice, come la classe che tra poco definiremo per memorizzare i nostri setting, oppure di tipo complesso e contenere al suo interno valori singoli costruiti con i tipi di dati di sistema e valori complessi, quali ad esempio altre entità o collezioni di entità. Siccome ci troveremo a creare varie entità da qui in avanti, speriamo di potervi mostrare con gli esempi qualcosa in più a riguardo.Se l'entità rappresenta la descrizione di un oggetto con le sue proprie caratteristiche, è possibile descrivere alcune caratteristiche comuni a tutte le entità. Il database di esempio della vecchia serie di articoli su MSDE conteneva il necessario a memorizzare una libreria di dischi o CD musicali. Se prendessimo in mano un CD e volessimo descriverlo come entità, potremmo dire che esso possiede delle proprietà: Titolo, Autore, Interprete, Casa Discografica, Categoria Musicale, Brani, Autore, Interprete; i brani e la casa discografica a loro volta sono Entità con delle proprietà specifiche: nome, cognome, biografia, oppure nome e indirizzo.
Ci sono alcune cose che accomunano tutte le entità che possiamo enumerare:
- Un'entità può essere comparata ad un altra entità dello stesso tipo
- Un'entità ha bisogno di un criterio che ne definisca la validità
- Un'entità deve poter essere fatta persistere su disco
- Un'entità può essere generata tramite interfaccia utente e deve quindi potersi collegare a dei componenti.
Per ora ci fermiamo qui. Le entità che genereremo nei nostri progetti saranno classi. Se vogliamo che tutte le classi di tipo entità rispettino queste quattro caratteristiche, dobbiamo fare in modo che abbiano qualcosa che forzi in ciascuna di esse questo comportamento. In altre parole, dobbiamo farle sottostare ad un contratto.
In OOP il contratto viene definito tramite una Interfaccia.
Quindi, prima di iniziare a definire una classe, definiremo una Interfaccia che tutte le entità da noi sviluppate dovranno implementare.
Nel Solution Explorer, clicchiamo 'destro' sul progetto TAndT.Base, aggiungiamo una nuova cartella di nome Entities usando Add New Folder, clicchiamo su questa ed usiamo Add New Item - Interface per aggiungere una interfaccia di nome IEntity.cs o IEntity.vb, scrivendovi all'interno il codice seguente:using System; using System.ComponentModel; namespace TAndT.Base.Entities { public interface IEntity : IComparable, INotifyPropertyChanged { bool IsValid(); } }Imports System Imports System.ComponentModel Namespace Entities Public Interface IEntity Inherits IComparable, INotifyPropertyChanged Function IsValid() As Boolean End Interface End NamespaceLe direttive Using (Imports) ci permettono di agganciare a questo file di programma i Namespace del Framework.Net che contengono le due ulteriori interfacce che imporremo a chi implementerà un oggetto IEntity.
Ogni IEntity dovrà per forza implementare l'interfaccia IComparable, la quale impone ad ogni classe che la implementa di contenere il metodo CompareTo:
public int CompareTo(object obj) { ... }Public Function CompareTo(ByVal obj As Object) As Integer _ ... End FunctionE dovrà implementare l'interfaccia INotifyPropertyChanged, che impone ad ogni classe che la implementa di generare ed implementare il seguente evento (e il suo generatore):
public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { ... }Public Event PropertyChanged(...) Private Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs) ... End SubQuesto evento dovrà essere scatenato all'interno della classe ogni qualvolta una proprietà pubblica cambia il proprio valore, e permetterà di notificare ai controlli dell'interfaccia utente che il contenuto della classe dati è stato modificato.
Il metodo IsValid, che ogni IEntity dovrà implementare, si occupa di validarne il contenuto e restituire true se la classe è valida, false se non lo è. Ad esempio, se costruiamo l'entity Autore, un oggetto della classe Autore sarà valido solo se il nome e il cognome dell'autore saranno due stringhe non vuote.
Definita l'interfaccia, ora definiamo la prima classe che la userà, la classe TAndTSetting.
La classe TAndTSetting
Per generare la classe, clic destro sulla cartella Entities e Add New Item - Classusing System; using System.ComponentModel; using System.Text; using System.Xml.Serialization;Imports System Imports System.ComponentModel Imports System.Text Imports System.Xml.SerializationLe direttive Using (Imports) ci permettono di evitare scritture verbose per i componenti che utilizzeremo all'interno della classe, in particolare ComponentModel ci fornisce le interfacce che implementeremo (IComparable e INotifyPropertyChange), Text ci fornisce lo StringBuilder e Xml.Serialization ci fornisce il necessario per implementare le funzioni di serializzazione e deserializzazione, che ci permetteranno di scrivere su disco e poi rileggere il contenuto della classe.
namespace TAndT.Base.Entities { ... }Namespace Entities ... End NamespaceSe scriviamo con Visual Studio, il namespace verrà generato automaticamente dal template della classe, in Visual Basic potrebbe essere necessario scriverlo manualmente. Ricordiamo che per Visual Basic si dovrà mettere solo il namespace Entities in quanto il prefisso TAndT.Base è già fornito dal Root Namespace impostato nella zona My Project.
Definiamo la nostra classe e, per indicare che sarà possibile farla persistere su disco, aggiungiamo l'attributo Serializable, ed il Namespace per fare in modo che il file XML prodotto sia univoco e riconoscibile. Indichiamo inoltre che implementeremo l'interfaccia IEntity:
[Serializable, XmlRoot(Namespace = "http://www.visual-basic.it")] public class TAndTSetting : IEntity { ... }<SerializableAttribute(), XmlRootAttribute(Namespace:="http://www.visual-basic.it")> _ Public Class TAndTSetting Implements IEntity ... End ClassDefiniamo la famigerata variabile mClassName, che ospiterà il nome della classe per la gestione eccezioni ed eventuali messaggi di avviso e le due variabili member che conterranno il nostro Setting, mName e mValue:
#region Variabili private private readonly static string mClassName = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name; private string mName; private string mValue; #endregion#Region "Variabili private" Private Shared ReadOnly mClassName As String = _ System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.Name #End RegionIniziamo (si vedrà poi perché) col definire l'evento PropertyChanged, richiesto dall'interfaccia INotifyPropertyChanged:
#region Events [Category("ClassEvents"), Description("Notifica la modifica di una property")] public event PropertyChangedEventHandler PropertyChanged; #endregion#Region "Eventi" <Category("ClassEvents"), Description("Notifica la modifica di una property")> _ Public Event PropertyChanged(ByVal sender As Object, _ ByVal e As System.ComponentModel.PropertyChangedEventArgs) _ Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged #End RegionE definiamo la procedura che permette di scatenarlo:
#region INotifyPropertyChanged Implementazione event handler protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { if (PropertyChanged != null) PropertyChanged(this, e); } #endregion#Region "Generatori di eventi" Private Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs) If Not e.PropertyName Is Nothing Then RaiseEvent PropertyChanged(Me, e) End If End Sub #End RegionDefiniamo le property pubbliche che espongono le due variabili member:
#region Proprietà public string Name { get { return mName; } set { mName = value; OnPropertyChanged(new PropertyChangedEventArgs("Name")); } } public string Value { get { return mValue; } set { mValue = value; OnPropertyChanged(new PropertyChangedEventArgs("Value")); } } #endregion#Region "Proprietà e campi relativi" Private mName As String Public Property Name() As String Get Return mName End Get Set(ByVal value As String) mName = value OnPropertyChanged(New PropertyChangedEventArgs("Name")) End Set End Property Private mValue As String Public Property Value() As String Get Return mValue End Get Set(ByVal value As String) mValue = value OnPropertyChanged(New PropertyChangedEventArgs("Value")) End Set End Property #End RegionOgnuna di esse scatenerà l'evento PropertyChanged (ecco perché l'abbiamo implementato prima) quando viene modificata, sicché la notifica di variazione possa essere intercettata dagli oggetti eventualmente utilizzati per la gestione della visualizzazione e della modifica dei setting a livello di interfaccia utente.
Ora definiamo il costruttore che inizializza le variabili member per il setting:
public TAndTSetting() { this.mName = string.Empty; this.mValue = string.Empty; }Sub New() mName = String.Empty mValue = String.Empty End SubE' importante definire un costruttore senza alcun parametro, perché le funzioni di serializzazione automatica del Framework .NET richiedono l'esistenza di un costruttore non parametrico. E' ammissibile che il costruttore senza parametri sia privato, se si preferisce non esporlo e rendere accessibile invece un costruttore con parametri, ma cerchiamo di non esagerare con le complicazioni, in questo nostro primo passo per arrivare ad una applicazione per la gestione di dati.
Ridefiniamo (override) il metodo ToString, che sostituisca la funzione base fornita dalla classe Object, da cui la nostra classe deriva, componendo invece una stringa che restituisca il nostro setting nel formato: NomeSetting=Valore.
public override string ToString() { try { return (string.Format("{0} = {1}", this.Name, this.Value)); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex); } }Public Overrides Function ToString() As String Try Return String.Format("{0} = {1}", Me.Name, Me.Value) Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod.Name _ + ": " + ex.Message, ex) End Try End FunctionOra dobbiamo implementare il metodo CompareTo, per rispondere al contratto dell'interfaccia IComparable. In questo caso, la comparazione permetterà di confrontare due oggetti non in base alla loro instanza, ma in base al loro contenuto, quindi CompareTo, dovrà comparare Name e Value di due oggetti e restituire il risultato.
Per prepararci questo compito, ridefiniamo alcuni metodi, cominciando con il metodo hashcode per questo oggetto, in questo caso semplicemente quello dell' Object, ma potrebbe anche essere calcolato in modo dipendente dal suo contenuto. Questo valore è univoco per ogni oggetto instanziato, si presume perché deriva dall'indirizzo di memoria dello heap dove l'oggetto è allocato.
public override int GetHashCode() { return (base.GetHashCode()); }Public Overrides Function GetHashCode() As Integer Return MyBase.GetHashCode End FunctionQuesto metodo ci serve per ridefinire il metodo di uguaglianza binaria (Equals) , che ci permette la comparazione a livello di oggetti,
public override bool Equals(object obj) { bool ret = false; if (this.GetHashCode() == obj.GetHashCode()) { ret = true; } return (ret); }Public Overrides Function Equals(ByVal obj As Object) As Boolean Dim ret As Boolean = False If Me.GetHashCode = obj.GetHashCode Then ret = True End If Return ret End FunctionEsso restituirà true solo se il confronto avviene tra due riferimenti allo stesso oggetto. Facciamo un esempio:
TAndTSetting FirstSetting = new TAndTSetting(); TAndTSetting SecondSetting = new TAndTSetting(); bool equals1 = FirstSetting.Equals( SecondSetting ); TAndTSetting ThirdSetting = FirstSetting; bool equals2 = FirstSetting.Equals( ThirdSetting );Dim firstSetting As TAndSetting = New TAndTSetting Dim secondSetting As TAndSetting = New TAndTSetting Dim equals1 As Boolean = firstSetting.Equals(secondSetting) Dim thirdSetting As TAndSetting = firstSetting Dim equals2 As Boolean = firstSetting.Equals(thirdSetting)la variabile equals1 avrà valore false, mentre equals2 avrà valore true, perché Equals compara i due oggetti, non il loro contenuto e, quindi, la prima variabile rappresenta la disuguaglianza tra due diversi oggetti, mentre la seconda rappresenta l'uguaglianza fra due diverse variabili che referenziano lo stesso oggetto.
Però ci sono dei casi particolari nella comparazione di stringhe, per cui preferiamo scrivere qualche riga di codice in più. Infatti, i due oggetti possono essere vuoti o contenere valori non validi, quali il null, pertanto è necessario gestirli. Per farlo, andiamo ad arricchire la nosta libreria base con una nuova classe e un nuovo metodo, anche questa una classe statica che inseriremo in TAndT.Base e si chiamerà CompareHelper:
using System; using System.Text; namespace TAndT.Base { public static class CompareHelper { #region Variabili private private static readonly string mClassName = _ System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name; #endregion #region String comparer public static int StringComparer(string pObjVal, string pNewVal) { int ret = 1; try { if (pObjVal != null) { if (pNewVal != null) { ret = pObjVal.CompareTo(pNewVal); } } else { if (pNewVal == null) { ret = 0; } else { ret = -1; } } } catch (Exception) { //Ignoriamo le eccezioni } return (ret); } #endregion } }Imports System Imports System.Text Public Class CompareHelper #Region "Variabili private" Private Shared ReadOnly mClassName As String = _ System.Reflection.MethodBase.GetCurrentMethod.ReflectedType.Name #End Region Public Shared Function StringComparer(ByVal firstValue As String, _ ByVal secondValue As String) As Integer Dim ret As Integer = 1 Try If Not firstValue Is Nothing Then If Not secondValue Is Nothing Then ret = firstValue.CompareTo(secondValue) End If Else If secondValue Is Nothing Then ret = 0 Else ret = -1 End If End If Catch ex As Exception End Try Return ret End Function End ClassCome la classe Warnings e la classe ExceptionHelper, anche questa è una classe statica, o, per chi scriverà in VB, una classe composta esclusivamente di metodi Shared, Vi invitiamo ad osservare che nel namespace System esiste già una classe StringComparer, la quale però è dedicata a compiti più elevati, quali la comparazione di stringhe che possono avere caratteristiche di cultura diverse, cosa che esula dalla nostra semplice necessità di comparare in modo sicuro due stringhe e controllare convenzionalmente i casi limite.
Vediamo che cosa fa la nostra funzione StringComparer (chiameremo Obj la prima stringa passata, la seconda New):
- Suppone per default che la stringa Obj sia maggiore della stringa New.
- Verifica se la stringa Obj è diversa da null (Nothing).
- Se Obj è diversa da null (Nothing), e New è diversa da null (Nothing), restuisce i risultato della comparazione tra le due stringhe.
- Se Obj è diversa da null (Nothing) e New è null (Nothing), restuisce il valore preimpostato, quindi Obj è considerata maggiore di null (Nothing)
- Se Obj è null (Nothing) e New è null (Nothing), restuisce un'uguaglianza, quindi le stringhe nulle sono considerate uguali.
- Se Obj è null (Nothing) e New non è null (Nothing), allora il valore di ritorno indica che New è maggiore di Obj.
Pertanto, facendo un esempio pratico:
CompareHelper.StringComparer( null, null ) restuisce 0 (stringhe uguali)
CompareHelper.StringComparer( null, "ciao" ) restuisce -1 (la stringa New è > della stringa Obj)
CompareHelper.StringComparer( "ciao", null ) restuisce 1 (la stringa New è < della stringa Obj)
CompareHelper.StringComparer( "ciao", "mandi" ) restuisce 1 (la stringa New è > della stringa Obj)
CompareHelper.StringComparer( "mandi", "ciao" ) restuisce -1 (la stringa New è < della stringa Obj)Perché ci prendiamo la briga di fare tutto questo? Perché in questo modo siamo noi a decidere come si comporteranno le comparazioni fra stringhe nelle nostre applicazioni. Infatti, la semplice CompareTo della classe string (String), quando uno degli elementi è null (Nothing) o lo sono entrambi, lancia una eccezione e, nella nostra pigrizia, invece di gestire le eccezioni in ogni classe, abbiamo deciso una convenzione comparativa per le stringhe nulle e ci siamo tolti il pensiero.
Anche in questo caso si tratta di una metodologia molto personale, quindi altri più bravi di noi possono considerare la cosa non elegante oppure sbagliata. Sabrina però, che scrive quotidianamente programmi su database, dove la comparazione di stringhe è una cosa comunissima, trova questa soluzione semplice e utile a risparmiare codice. Questa classe è una Business Rule che Sabrina ha deciso ed impone a tutte le soluzioni software che sviluppa (Diego si adegua volentieri).Per fare il test e mostrare cosa accade quando al metodo CompareTo della classe string viene passato un null, abbiamo aggiunto alla nostra classe FrmMain la direttiva:
using TAndT.Base.Entities;Imports TAndT.Base.Entities;e poi un menuItem, il cui Text è 'Test Compare To', che quindi si chiama testCompareToStripMenuItem e il cui codice è il seguente:
private void testCompareToStripMenuItem_Click(object sender, EventArgs e) { try { string objString = null; string newString = null; int val = objString.CompareTo(newString); } catch (Exception ex) { Warnings.Error(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub TestCompareToToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles TestCompareToToolStripMenuItem.Click Try Dim firstString As String = Nothing Dim secondString As String = Nothing Dim ret As Integer = firstString.CompareTo(secondString) Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubIl risultato di questo codice è il seguente:
Aggiungiamo ancora una opzione al menu e testiamo invece il nostro metodo StringComparer: l'opzione di menu ha Text 'Test StringComparer', quindi si chiamerà testStringComparerStripMenuItem e il suo codice è il seguente:
private void testStringComparerToolStripMenuItem_Click(object sender, EventArgs e) { try { StringBuilder sb = new StringBuilder(); string objString = null; string newString = null; sb.AppendLine("Test CompareHelper.StringComparer"); sb.AppendFormat("objString null comparato a newString null restituisce: {0}", CompareHelper.StringComparer(objString, newString)); sb.AppendLine(); newString = "ciao"; sb.AppendFormat("objString null comparato a newString \"ciao\" restituisce: {0}", CompareHelper.StringComparer(objString, newString)); sb.AppendLine(); objString = "ciao"; sb.AppendFormat("objString \"ciao\" comparato a newString \"ciao\" restituisce: {0}", CompareHelper.StringComparer(objString, newString)); sb.AppendLine(); newString = null; sb.AppendFormat("objString \"ciao\" comparato a newString null restituisce: {0}", CompareHelper.StringComparer(objString, newString)); sb.AppendLine(); newString = "mandi"; sb.AppendFormat("objString \"ciao\" comparato a newString \"mandi\" restituisce: {0}", CompareHelper.StringComparer(objString, newString)); sb.AppendLine(); objString = "mandi"; newString = "ciao"; sb.AppendFormat("objString \"mandi\" comparato a newString \"ciao\" restituisce: {0}", CompareHelper.StringComparer(objString, newString)); sb.AppendLine(); Warnings.Info(sb.ToString()); } catch (Exception ex) { Warnings.Error(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub TestStringComparerToolStripMenuItem_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles TestStringComparerToolStripMenuItem.Click Try Dim sb As New StringBuilder Dim firstString As String = Nothing Dim secondString As String = Nothing sb.AppendLine("Test CompareHelper.StringComparer") sb.AppendFormat("firstString null comparato a secondString null restituisce: {0}", _ CompareHelper.StringComparer(firstString, secondString)) sb.AppendLine() secondString = "ciao" sb.AppendFormat("firstString null comparato a secondString ""ciao"" restituisce: {0}", _ CompareHelper.StringComparer(firstString, secondString)) sb.AppendLine() firstString = "ciao" sb.AppendFormat("firstString ""ciao"" comparato a secondString ""ciao"" restituisce: {0}", _ CompareHelper.StringComparer(firstString, secondString)) sb.AppendLine() secondString = Nothing sb.AppendFormat("firstString ""ciao"" comparato a secondString null restituisce: {0}", _ CompareHelper.StringComparer(firstString, secondString)) sb.AppendLine() secondString = "mandi" sb.AppendFormat("firstString ""ciao"" comparato a secondString ""mandi"" restituisce: {0}", _ CompareHelper.StringComparer(firstString, secondString)) sb.AppendLine() firstString = "mandi" secondString = "ciao" sb.AppendFormat("firstString ""mandi"" comparato a secondString ""ciao"" restituisce: {0}", _ CompareHelper.StringComparer(firstString, secondString)) sb.AppendLine() Warnings.Info(sb.ToString()) Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubIl risultato del clic su questa opzione di menu è il seguente:
Proseguiamo ora con la classe TAndTSetting, e sviluppiamo il metodo CompareTo.
public int CompareTo(object obj) { try { int comparator = -1; if (obj is TAndTSetting) { TAndTSetting val = (TAndTSetting)obj; comparator = CompareHelper.StringComparer(this.Name, val.Name); if (comparator == 0) { comparator = CompareHelper.StringComparer(this.Value, val.Value); } } return (comparator); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function CompareTo(ByVal obj As Object) As Integer _ Implements System.IComparable.CompareTo Try Dim comparator As Integer = -1 If TypeOf obj Is TAndTSetting Then Dim val As TAndTSetting = DirectCast(obj, TAndTSetting) comparator = CompareHelper.StringComparer(Me.Name, val.Name) If comparator = 0 Then comparator = CompareHelper.StringComparer(Me.Value, val.Value) End If End If Return comparator Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionCome possiamo vedere, l'implementazione della comparazione fra due TAndTSetting, dopo aver predisposto il nostro helper, è piuttosto semplice. Verifica se l'oggetto passato come parametro è un TAndTSetting; se non lo fosse, il nostro TAndTSetting sarebbe per convenzione più piccolo e quindi il risultato sarebbe -1.
Se gli oggetti sono dello stesso tipo, quindi comparabili, compariamo la proprietà Name, se i nomi sono diversi, viene restituito il risultato di questa comparazione, se i nomi sono uguali, si procede a comparare la proprietà Value. In questo modo, due TAndTSetting sono uguali se hanno lo stesso nome e lo stesso valore.Ed ora, per chiudere il cerchio, visto che potremo avere la necessità di verificare una uguaglianza o una disuguaglianza fra due setting come condizione di una if nel nostro codice, usiamo la funzione CompareTo appena implementata per aggiungere alla nostra classe gli operatori di uguaglianza e disuguaglianza.
public static bool operator ==(TAndTSetting pX, TAndTSetting pY) { try { bool ret = false; object oX = (object)pX; object oY = (object)pY; if (oX == null && oY == null) { ret = true; } else { if (oX != null && oY != null) { if (pX.CompareTo(pY) == 0) { ret = true; } } } return (ret); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } } public static bool operator !=(TAndTSetting pX, TAndTSetting pY) { return (!(pX == pY)); }Public Shared Operator =(ByVal left As TAndTSetting, ByVal right As TAndTSetting) As Boolean Try Dim ret As Boolean = False Dim oX As Object = DirectCast(left, Object) Dim oY As Object = DirectCast(right, Object) If oX Is Nothing And oY Is Nothing Then ret = True Else If Not (oX Is Nothing OrElse oY Is Nothing) Then If left.CompareTo(right) = 0 Then ret = True End If End If End If Return ret Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End Operator Public Shared Operator <>(ByVal left As TAndTSetting, ByVal right As TAndTSetting) As Boolean Return Not (left = right) End OperatorL'implementazione dell'operatore di uguaglianza che possiamo osservare, così come l'operatore di comparazione fra stringhe, controlla e gestisce in modo convenzionale l'uguaglianza fra oggetti null (Nothing) pertanto, se i nostri due oggetti valgono entrambi null (Nothing) sono uguali, se solo uno dei due è diverso da null (Nothing) sono diversi, se entrambi sono diversi da null (Nothing) sono uguali se la loro comparazione ritorna una uguaglianza. L'operatore di disuguaglianza è una semplice negazione dell'operatore di uguaglianza.
L'interfaccia IEntity ci impone di implementare un ultimo metodo nella nostra classe, per soddisfare il nostro contratto, questo metodo, IsValid, può essere chiamato in ogni momento dal programma che userà la nostra Entity, per verificare che il contenuto della classe sia valido:
public bool IsValid() { try { return (this.Name != null && this.Name.Trim().Length > 0); } catch (Exception ex) { throw new ApplicationException(" " + mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name, ex); } }Public Function IsValid() As Boolean Implements IEntity.IsValid Try Return Not (Me.Name Is Nothing OrElse Me.Name.Trim.Length = 0) Catch ex As Exception Throw New ApplicationException(" " + mClassName + "." _ + System.Reflection.MethodBase.GetCurrentMethod().Name, ex) End Try End FunctionIn questo caso, il vincolo di validità che imponiamo è semplice, esattamente come semplice è la classe che abbiamo generato, quindi un TAndTSetting è valido se ha un nome non nullo e contenente almeno un carattere alfanumerico.
Proviamo la nostra classe, creando sulla FrmMain un nuovo menù a livello di MenuBar, cui assegneremo il Text 'Setting Manager Tests', da cui il nome settingManagerTestsToolStripMenuItem; aggiungiamo una nuova opzione dentro al menu, con Text uguale a 'Test TAndTSetting', quindi con nome testTAndTSettingToolStripMenuItem; implementiamo il suo codice di evento:
private void testTAndTSettingToolStripMenuItem_Click(object sender, EventArgs e) { try { TAndTSetting mySetting = new TAndTSetting(); StringBuilder sb = new StringBuilder(); sb.AppendLine("Test Classe TAndTSetting"); sb.AppendFormat("La classe vuota contiene il nome <{0}> e il valore <{1}>", mySetting.Name, mySetting.Value); sb.AppendLine(); sb.AppendFormat("La funzione IsValid restituisce <{0}> ", mySetting.IsValid()); sb.AppendLine(); mySetting.Name = "NomeUtente"; mySetting.Value = "Sabrina Cosolo"; sb.AppendLine("Inizializzando le property Name e Value otteniamo che: "); sb.AppendFormat("La classe ora contiene il nome <{0}> e il valore <{1}>", mySetting.Name, mySetting.Value); sb.AppendLine(); sb.AppendFormat("La funzione IsValid restituisce <{0}> ", mySetting.IsValid()); sb.AppendLine(); sb.AppendFormat("La funzione ToString restituisce <{0}>", mySetting.ToString()); sb.AppendLine(); sb.AppendLine("Dichiariamo un nuovo setting e assegnamogli il vecchio setting"); TAndTSetting myNewSetting = mySetting; sb.AppendFormat("La funzione Equals restituisce: <{0}>", mySetting.Equals(myNewSetting)); sb.AppendLine(); sb.AppendLine("Generiamo un nuovo setting e riproviamo."); myNewSetting = new TAndTSetting(); myNewSetting.Name = "NomeUtente"; myNewSetting.Value = "Sabrina Cosolo"; sb.AppendFormat("La nuova classe ora contiene il nome <{0}> e il valore <{1}>", myNewSetting.Name, myNewSetting.Value); sb.AppendLine(); sb.AppendFormat("La funzione Equals restituisce: <{0}>", mySetting.Equals(myNewSetting)); sb.AppendLine(); sb.AppendFormat("La funzione CompareTo restituisce: <{0}>", mySetting.CompareTo(myNewSetting)); sb.AppendLine(); sb.AppendLine("Facciamo una modifica."); myNewSetting.Value = "Patrizia Cosolo"; sb.AppendFormat("La nuova classe ora contiene il nome <{0}> e il valore <{1}>", myNewSetting.Name, myNewSetting.Value); sb.AppendLine(); sb.AppendFormat("La funzione CompareTo restituisce: <{0}>", mySetting.CompareTo(myNewSetting)); sb.AppendLine(); sb.AppendFormat("L'operatore di uguaglianza (== [=]) restituisce: <{0}>", mySetting == myNewSetting); sb.AppendLine(); sb.AppendFormat("L'operatore di differenza (!=[<>])restituisce: <{0}>", mySetting != myNewSetting); sb.AppendLine(); Warnings.Info(sb.ToString()); } catch (Exception ex) { Warnings.Error(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex); } }Private Sub TestTAndTSettingToolStripMenuItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles TestTAndTSettingToolStripMenuItem.Click Try Dim mySetting As TAndTSetting = New TAndTSetting Dim sb As StringBuilder = New StringBuilder sb.AppendLine("Test Classe TAndTSetting") sb.AppendFormat("La classe vuota contiene il nome <{0}> e il valore <{1}>", mySetting.Name, _ mySetting.Value) sb.AppendLine() sb.AppendFormat("La funzione IsValid restituisce <{0}> ", mySetting.IsValid()) sb.AppendLine() mySetting.Name = "NomeUtente" mySetting.Value = "Diego Cattaruzza" sb.AppendLine("Inizializzando le property Name e Value otteniamo che: ") sb.AppendFormat("La classe ora contiene il nome <{0}> e il valore <{1}>", mySetting.Name, _ mySetting.Value) sb.AppendLine() sb.AppendFormat("La funzione IsValid restituisce <{0}> ", mySetting.IsValid()) sb.AppendLine() sb.AppendFormat("La funzione ToString restituisce <{0}>", mySetting.ToString()) sb.AppendLine() sb.AppendLine("Dichiariamo un nuovo setting e assegnamogli il vecchio setting") Dim myNewSetting As TAndTSetting = mySetting sb.AppendFormat("La funzione Equals restituisce: <{0}>", mySetting.Equals(myNewSetting)) sb.AppendLine() sb.AppendLine("Generiamo un nuovo setting e riproviamo.") myNewSetting = New TAndTSetting() myNewSetting.Name = "NomeUtente" myNewSetting.Value = "Diego Cattaruzza" sb.AppendFormat("La nuova classe ora contiene il nome <{0}> e il valore <{1}>", _ myNewSetting.Name, myNewSetting.Value) sb.AppendLine() sb.AppendFormat("La funzione Equals restituisce: <{0}>", mySetting.Equals(myNewSetting)) sb.AppendLine() sb.AppendFormat("La funzione CompareTo restituisce: <{0}>", mySetting.CompareTo(myNewSetting)) sb.AppendLine() sb.AppendLine("Facciamo una modifica.") myNewSetting.Value = "Diana Cattaruzza" sb.AppendFormat("La nuova classe ora contiene il nome <{0}> e il valore <{1}>", _ myNewSetting.Name, myNewSetting.Value) sb.AppendLine() sb.AppendFormat("La funzione CompareTo restituisce: <{0}>", mySetting.CompareTo(myNewSetting)) sb.AppendLine() sb.AppendFormat("L'operatore di uguaglianza (=) restituisce: <{0}>", mySetting = myNewSetting) sb.AppendLine() sb.AppendFormat("L'operatore di differenza (<>)restituisce: <{0}>", mySetting <> myNewSetting) sb.AppendLine() Warnings.Info(sb.ToString()) Catch ex As Exception Warnings.Errore(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex) End Try End SubQuesto semplice test prova tutte le funzionalità della classe e pone i risultati in una stringa che mostra, con il seguente risultato:
Appuntamento alla prossima puntata
La classe TAndTSetting è terminata. Abbiamo creato un oggetto in grado di contenere un valore di configurazione (limitandoci per il momento a memorizzare stringhe).
Ovviamente per il nostro progetto avremo bisogno di memorizzare più di un valore, infatti ne abbiamo elencati cinque solo per memorizzare i dati di connessione. Quindi ci serve un modo per memorizzare una collezione di TAndTSetting e infatti collezione è la specie a cui appartiene la nostra prossima classe.
Il codice fin qui sviluppato è scaricabile dall'area Download.
Potete scrivere Feedback (commenti, critiche, suggerimenti, correzioni) sul blog di Sabrina.