Press "Enter" to skip to content

1 – Classi di uso comune

Una classe per operare sulle stringhe

Un articolo dedicato ad una classe di uso generico dedicata ad alcune particolari operazioni pilotate sulle stringhe. Fa parte di alcune classi ad uso generico, i cosiddetti Helper che in seguito serviranno all’interno di vari progetti didattici e potranno ovviamente essere incorporate nelle librerie di classi dai nostri lettori.à Ogni programmatore che intenda sviluppare più di una applicazione nella propria carriera, incontra durante la creazione del codice degli oggetti di uso comune, usati N volte all’interno di qualsiasi progetto. Questa serie di articoli, ospiterà questo “Fritto Misto” di classi utili per vari scopi e vari usi, che daranno origine ad una, più probabilmente una serie, di librerie di classi che saranno utilizzate in seguito nei progetti didattici che costruiranno vere e proprie applicazioni.

Introduzione

Sto preparando una piccola applicazione da utilizzare come progetto didattico per mostrare come costruire un intera applicazione in .NET, pertanto ho deciso di creare tutto il codice della stessa per la versione 1.0 per decidere poi come costruire la serie degli articoli.

Progettando questa applicazione, pure piuttosto semplice nella sua forma, mi sono resa conto che per costruirla servono una serie di classi helper che forniscono vari tipi di “servizi” all’applicazione e al suo sviluppo. Visto e considerato che queste classi avranno modo di essere riutilizzate N volte in tutti i nostri futuri progetti didattici, e visto che non hanno una collocazione precisa all’interno di un singolo progetto, ho deciso di aprire una serie di articoli su di esse, che ne spieghino funzione e codice per poi metterle da parte e usarle quando necessarie inserendo nell’articolo ove sono usate un semplice richiamo alla spiegazione del loro funzionamento.

Detto questo iniziamo, le classi non sono spiegate in un ordine preciso, se non quello in cui sono capitate sotto mano a chi scrive, il loro numero, all’interno di ogni articolo, dipenderà semplicemente da una arbitraria decisione di chi scrive. (ovvero continuo fino a che mi stufo )

La classe StringHelper

Per quanto la classe string (String) offra una notevole quantità di Helper preconfezionati per tutte o quasi le operazioni sulle stringhe, alcune cose sono lasciate alla decisione del programmatore e devono essere costruite in base a regole di Business che intende impostare nelle proprie applicazioni. Altre funzionalità che riguardano le stringhe, sono dettate dalla necessit� del momento, nella nostra classe inseriremo una serie di metodi che mi sono tornati utili, come sempre non sono scritti nell’unico modo possibile, il codice non è il più bello, il più performante, il più straordinario, ma fa il suo lavoro.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Reflection;
namespace Dnw.Base
{
    public static class StringHelper
        {
        }
}
Imports System 
Imports System.Collections.Generic 
Imports System.Collections.Specialized 
Imports System.Text 
Imports System.Reflection 

Namespace Dnw.Base 
    Public NotInheritable Class StringHelper 
        Private Sub New() 
        End Sub 
     End Class 
End Namespace

La struttura di base della classe, in VB, il Namespace è Impostato come RootNamespace sul progetto, pertanto non si vede. Per convenzione, il primo livello di namespace di tutte le applicazioni che io scriverò nei miei articoli per DotnetWork, sarà Dnw.

La classe base per l’helper, come sarè per molti degli helper che scriveremo,è static, in VB non è possibile creare una classe Shared, ma è possibile simularla creando una classe con costruttore statico oppure, si pu� utilizzare un Modulo.

Perché usare una classe Statica? Semplicemente perché conterré esclusivamente metodi, ed eventualmente costanti, non c’è alcun oggetto da istanziare.

private readonly static string mClassName =
            System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;
Private Shared ReadOnly mClassName As String _
	= System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name

In tutte le classi che scrivo, troverete questa variabile, il codice che contiene, tramite Reflection ottiene il nome della classe, perché scomodare la Reflection quando bastava scrivere “StringHelper”, ma perché siamo programmatori, e se domattina ci vien voglia di cambiare nome alla classe, in questo modo non dobbiamo fare nulla. Se vi state chiedendo che cosa diamine sia la Reflection, vi invito a leggere i libri di Francesco Balena e a visitare il sito di Lutz Roeder ove questo argomento non pertinente questo articolo é spiegato in modo esteso, eventualmente parleremo della reflection in futuro.

Lo scopo dell’mClassName, é fornire il nome della classe sorgente alle eccezioni sollevate dal codice della classe stessa.

Il metodo Compare

public static int Compare(string pStringSource, string pStringTarget)
        {
            int ret = 1;
            try
            {
                if (pStringSource != null)
                {
                    if (pStringTarget != null)
                    {
                        ret = pStringSource.CompareTo(pStringTarget);
                    }
                }
                else
                {
                    if (pStringTarget == null)
                    {
                        ret = 0;
                    }
                    else
                    {
                        ret = -1;
                    }
                }

            }
            catch (Exception)
            {
                //Ignoriamo le eccezioni
            }
            return (ret);
        }
Public Shared Function Compare(ByVal pStringSource As String, ByVal pStringTarget As String) As Integer 
        Dim ret As Integer = 1 
        Try 
            If pStringSource IsNot Nothing Then 
                If pStringTarget IsNot Nothing Then 
                    ret = pStringSource.CompareTo(pStringTarget) 
                End If 
            Else 
                If pStringTarget Is Nothing Then 
                    ret = 0 
                Else 
                    ret = -1 
                End If 
                
            End If 
                'Ignoriamo le eccezioni 
        Catch generatedExceptionName As Exception 
        End Try 
        Return (ret) 
    End Function

Il primo metodo della classe helper, é un metodo Business, che effettua la comparazione fra due stringhe secondo regole arbitrarie da noi stabilite, questo metodo lo utilizzeremo spesso per la generazione dei metodi di comparazione fra oggetti. Le regole che abbiamo imposto alla nostra comparazione sono le seguenti:

  • Una stringa null é minore di qualsiasi altra stringa.
  • Una stringa non nulla é maggiore di una stringa null.
  • Per tutte le stringhe non nulle ovviamente vale la comparazione standard del framework.

Invito i curiosi a fare un giro su MSDN e dare un’occhiata alla classe StringComparer fornita dal framework che permette una comparazione molto sofisticata fra stringhe appartenenti a culture diverse, per chi come noi almeno per ora si occuperà di stringhe di base, con caratteri latini, l’uso di questa classe non é necessario, però, potrebbe tornarci utile in futuro, ecco perché l’ho citata e vi invito a studiarla.

Il metodo GetObjString

private const string FMP_Classe = "Classe: {0} ";
private const string FMP_PropertyList = "{0}: {1}";
public static string GetObjString(object obj)
        {
            try
            {
                StringBuilder sb = new StringBuilder();
                PropertyInfo[] propInfo = obj.GetType().GetProperties();

                int ip = 0;

                sb.AppendFormat(FMP_Classe, obj.GetType().Name);
                sb.Append(Environment.NewLine);

                foreach (PropertyInfo pi in propInfo)
                {
                    if (pi.CanRead)
                    {
                        //Workaround x eccezioni su alcuni oggetti
                        //---INIZIO: Workaround -------------------------
                        object objProp = null;
                        try
                        {
                            objProp = propInfo[ip].GetValue(obj, null);
                        }
                        catch (System.Reflection.TargetParameterCountException)
                        {
                        }
                        //---FINE: Workaround ---------------------------

                        string sProp = string.Empty;

                        if (objProp != null)
                        {
                            sProp = objProp.ToString();
                        }

                        sb.AppendFormat(FMP_PropertyList, propInfo[ip].Name, sProp);
                        sb.Append(Environment.NewLine);
                    }

                    ip++;
                }

                return sb.ToString();
            }
            catch (Exception ex)
            {
                throw new ApplicationException(" " + mClassName + 
                "." + System.Reflection.MethodBase.GetCurrentMethod().Name 
                + ": " + ex.Message, ex);
            }
        }
Public Shared Function GetObjString(ByVal obj As Object) As String 
    Try 
        Dim sb As New StringBuilder() 
        Dim propInfo As PropertyInfo() = obj.[GetType]().GetProperties() 
        
        Dim ip As Integer = 0 
        
        sb.AppendFormat(FMP_Classe, obj.[GetType]().Name) 
        sb.Append(Environment.NewLine) 
        
        For Each pi As PropertyInfo In propInfo 
            If pi.CanRead Then 
                'Workaround x eccezioni su alcuni oggetti 
                '---INIZIO: Workaround ---------------------------- 
                Dim objProp As Object = Nothing 
                Try 
                    objProp = propInfo(ip).GetValue(obj, Nothing) 
                Catch generatedExceptionName As System.Reflection.TargetParameterCountException 
                End Try 
                '---FINE: Workaround ------------------------------ 
                
                Dim sProp As String = String.Empty 
                
                If objProp IsNot Nothing Then 
                    sProp = objProp.ToString() 
                End If 
                
                sb.AppendFormat(FMP_PropertyList, propInfo(ip).Name, sProp) 
                sb.Append(Environment.NewLine) 
            End If 
            
            ip += 1 
        Next 
        
        Return sb.ToString() 
    Catch ex As Exception 
               Throw New ApplicationException(" " + mClassName + "." + _
        System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex) 
    End Try 
End Function

Un metodo che ci permette di creare una stringa di base contenente la lista delle proprietà pubbliche di un oggetto ed il loro valore. Per generare il nostro metodo, utilizziamo uno StringBuilder, per rilevare le property dell’oggetto di cui costruiamo la lista, utilizziamo il metodo GetType della classe Object per ottenere la classe Type associata all’oggetto e il metodo GetProperties della classe Type per ottenere un array di PropertyInfo. Che ci permette di ottenere il nome ed il valore di ogni proprietà di qualsiasi oggetto. Come potete notare, abbiamo inserito un workaround per evitare un errore che si può verificare in alcuni oggetti.

Questo metodo torna utile soprattuto per la costruzione del metodo ToString di default per le classi di tipo Entity che implementeremo nelle nostre applicazioni, e per creare il metodo ToString per le collezioni.

In questa classe é introdotto un altro elemento ricorsivo nelle mie classi, che anche in questo caso non é il migliore ne l’unico, ma é un metodo che io trovo utile. Si tratta del rilancio delle eccezioni non gestite inserito nel Catch di tutti i metodi di libreria o dei moduli applicativi salvo quelli ove sarebbe inutile o dannoso.

Se in un metodo, qualora si verificasse un’eccezione non fosse possibile gestirla in modo specializzato, io ho l’abitudine di registrare l’eccezione e rimandarla indietro lungo lo stack di chiamata agganciando informazioni relative alla classe ed al metodo ove l’eccezione ha avuto origine, utilizzando un metodo della classe ExceptionHelper, che sarà discusso in uno dei prossimi articoli di questa serie, utilizzeremo una funzione ricorsiva per gestire la visualizzazione di questa catena di eccezioni.

Il metodo IndexOfList

public static int IndexOfList(string pStringValue, bool pExact, string[] pStringList)
        {
            int retIsInList = -1;
            if (pStringList.Length > 0)
            {
                if (pExact)
                {
                    for (int i = 0; i < pStringList.Length; i++)
                    {
                        if (pStringValue == pStringList[i])
                        {
                            retIsInList = i;
                            break;
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < pStringList.Length; i++)
                    {
                        if (pStringList[i].Contains(pStringValue))
                        {
                            retIsInList = i;
                            break;
                        }
                    }
                }
            }
            return (retIsInList);
        }
Public Shared Function IndexOfList(ByVal pStringValue As String, ByVal pExact As Boolean, _
	ByVal pStringList As String()) As Integer 
        Dim retIsInList As Integer = -1 
        If pStringList.Length > 0 Then 
            If pExact Then 
                For i As Integer = 0 To pStringList.Length - 1 
                    If pStringValue = pStringList(i) Then 
                        retIsInList = i 
                        Exit For 
                    End If 
                Next 
            Else 
                For i As Integer = 0 To pStringList.Length - 1 
                    If pStringList(i).Contains(pStringValue) Then 
                        retIsInList = i 
                        Exit For 
                    End If 
                Next 
            End If 
        End If 
        Return (retIsInList) 
    End Function

Questo metodo molto semplice, permette di verificare se una stringa é contenuta in una lista di stringhe, in modo esatto ovvero una delle stringhe della lista deve essere uguale a quella cercata, oppure in modo non esatto, ovvero una delle stringhe della lista deve contenere la stringa al suo interno anche se come porzione della stessa.

Lo scopo di questa funziona é rendersi utile assieme alle funzioni che seguiranno, per lavorare con insiemi di stringhe molto piccoli ove sarebbe oneroso e inutile implementare Sort o altri meccanismi di ricerca avanzati. E’ chiaro che applicarla a una lista con 1000 o 10000 più elementi sarebbe inefficiente.

Ma dobbiamo ricordarci sempre che il codice dipende dal contesto in cui si usa, non esiste codice perfetto per ogni situazione, e soprattutto che ogni codice che ci sembra buono oggi, fra tre mesi lo sostituiremo con qualcosa di completamente diverso, più efficiente, più veloce, perché ovviamente avremo imparato cose nuove.

Il metodo IsInList

public static bool IsInList(string pStringValue, bool pExact, string[] pStringList)
        {
            return (IndexOfList(pStringValue, pExact, pStringList) > -1);
        }
Public Shared Function IsInList(ByVal pStringValue As String, ByVal pExact As Boolean, _
	 ByVal pStringList As String()) As Boolean 
        Return (IndexOfList(pStringValue, pExact, pStringList) > -1) 
    End Function

Un uso utile del metodo precedente, verificare se una stringa � contenuta in un’array di stringhe.

public static bool IsInList(string pStringValue, bool pExact, StringCollection pStringColl)
        {
            string[] strings = new string[pStringColl.Count];
            pStringColl.CopyTo(strings, 0);
            return IsInList(pStringValue, pExact, strings);
        }
Public Shared Function IsInList(ByVal pStringValue As String, ByVal pExact As Boolean, _
	 ByVal pStringColl As StringCollection) As Boolean 
        Dim strings As String() = New String(pStringColl.Count - 1) {} 
        pStringColl.CopyTo(strings, 0) 
        Return IsInList(pStringValue, pExact, strings) 
    End Function

Un overload del metodo precedente, per utilizzare i due metodi precedenti con una collezione, e scoprire il metodo CopyTo, perfetto per risolvere la cosa con una riga di codice.

Il metodo IsNullOrTrimEmpty

public static bool IsNullOrTrimEmpty(string pStringValue)
        {
            return (pStringValue == null || pStringValue.Trim().Length == 0);
        }
Public Shared Function IsNullOrTrimEmpty(ByVal pStringValue As String) As Boolean 
        Return (pStringValue Is Nothing OrElse pStringValue.Trim().Length = 0) 
    End Function

Anche questo è un metodo prettamente business, nei miei programmi che usualmente manipolano dati, le stringhe sono vuote quando sono nulle oppure quando contengono solo spazi, pertanto, con una riga di codice, posso controllare quando una stringa è vuota secondo le mie regole di business.

Conclusioni

La prima serie di Helper Finisce qui, il progetto contenente tutte le classi dei cinque articoli è disponibile per il download dal link sottostante.

Per qualsiasi Feedback, Ulteriore domanda, Chiarimento, oppure se trovate qualche errore usate direttamente tastino con la busta per mandarmi un messaggio.