Press "Enter" to skip to content

5 – Classi di uso comune

In questa quinta puntata, molto semplice e veloce, definiamo alcuni tipi di eccezione personalizzata e creiamo un metodo per costruire una stringa messaggio che visualizzi tutto il percorso di un eccezione all’interno di una applicazione.

Introduzione

Le linee guida sulla scrittura di codice .NET dicono che quando in una porzione di codice c’è la possibilità che un valore errato possa provocare un comportamento sbagliato è opportuno gestire il caso e lanciare un’eccezione personalizzata derivata da ApplicationException che possa quindi essere gestita, oltre a questo, è suggerito di gestire nelle funzioni tutte le eccezioni a cui sia possibile ovviare e rilanciare al chiamante quelle non gestibili.

Se avete già letto qualcuno dei miei articoli passati sapete che è mia abitudine rilanciare sempre le eccezioni non gestite aggiungendo ad esse un messaggio che indica la classe ed il metodo (o la property) di provenienza.

Si tratta di un metodo che ho scelto io, ciascuno può decidere come preferisce lavorare, nelle scorse puntate abbiamo implementato la classe EventLogger e aggiungeremo anche l’invio di un messaggio di log oltre al rilancio dell’eccezione così da poter tracciare completamente le nostre applicazioni.

In questo piccolo articolo vediamo come generare delle eccezioni personalizzate che abbiamo utilizzato o utilizzeremo nella nostra libreria di classi base e nel resto delle nostre applicazioni.

La classe NoFileException

using System;
using System.Collections.Generic;
using System.Text;

namespace Dnw.Base
{

    public class NoFileException : System.ApplicationException
    {

        public NoFileException(string pMessage)
            : base(pMessage)
        {

               } 

    }
}
Imports System 
Imports System.Collections.Generic 
Imports System.Text 

    Public Class NoFileException 
        Inherits System.ApplicationException 

        Public Sub New(ByVal pMessage As String) 

            MyBase.New(pMessage) 
        End Sub 

    End Class

Per generare un’eccezione personalizzata, ci basta semplicemente creare una classe che eredita da ApplicationException, se non dobbiamo fornire alcun dato particolare, basta utilizzare un try catch che la intercetta e il suo scopo è raggiunto.

Questa prima eccezione, ci servirà all’interno della classe che spiegheremo nel prossimo articolo, l’ultimo di questa serie di classi base, che introdurrà una serie di metodi che utilizzeremo per lavorare con il file system di Windows. Al termine, avremo le classi indispensabili a iniziare a sviluppare qualcosa di funzionante. Questa eccezione sarà utilizzata per segnalare che non abbiamo trovato un file che ci è stato richiesto.

La classe EmptyPathException

using System;
using System.Collections.Generic;
using System.Text;

namespace Dnw.Base
{
    public class EmptyPathException : System.ApplicationException
    {
        public EmptyPathException(string pMessage)
            : base(pMessage)
        {

        } 

    }
}
Imports System 
Imports System.Collections.Generic 
Imports System.Text 

   Public Class EmptyPathException 
        Inherits System.ApplicationException 
        Public Sub New(ByVal pMessage As String) 

            MyBase.New(pMessage) 
        End Sub 

    End Class

La seconda eccezione personalizzata ci serve per segnalare un parametro mancante o vuoto ove è richiesto di indicare un path.

La classe EmptyParameterException

using System;
using System.Collections.Generic;
using System.Text;

namespace Dnw.Base
{
    public class EmptyParameterException : System.ApplicationException
    {
        public EmptyPathException(string pMessage)
            : base(pMessage)
        {

        } 

    }
}
Imports System 
Imports System.Collections.Generic 
Imports System.Text 

   Public Class EmptyParameterException 
        Inherits System.ApplicationException 
        Public Sub New(ByVal pMessage As String) 

            MyBase.New(pMessage) 
        End Sub 

    End Class

La terza eccezione personalizzata sarà usata per segnalare un parametro di tipo stringa che deve obbligatoriamente contenere una stringa non vuota e invece contiene una stringa nulla o vuota.

La classe DuplicateKeyException

using System;
using System.Collections.Generic;
using System.Text;

namespace Dnw.Base.Collections
{
    public class DuplicateKeyException : System.ApplicationException
    {
        public DuplicateKeyException(string pMessage)
            : base(pMessage)
        {

        } 

    }
}
Imports System 
Imports System.Collections.Generic 
Imports System.Text 

Namespace "Collections"

    Public Class DuplicateKeyException 
        Inherits System.ApplicationException 
        Public Sub New(ByVal pMessage As String) 

            MyBase.New(pMessage) 
        End Sub 

    End Class 

End Namespace

La quarta ed ultima eccezione personalizzata, ci servirà a segnalare eventuali chiavi duplicate nelle EntityCollections, ove prevediamo di inserire per ogni Entity un campo o una combinazione di campi che compongano un ID univoco.

C’è ancora una sola cosa che si può notare in queste classi elementari, il fatto che il nome termina con la parola Exception, anche questa è una convenzione, indicata nelle linee guida di Microsoft, però è discrezione del programmatore rispettarla, potete chiamare le vostre eccezioni come preferite, il suffisso è solo un modo per renderle facilmente riconoscibili. Un’altra delle linee guida dice che le eccezioni dovrebbero essere definite ove usate, ecco perché il Namespace che abbiamo dato loro è lo stesso delle classi (o delle classi base) che le utilizzeranno.

La classe ExceptionHelper

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace Dnw.Base
{
    public static class ExceptionHelper
    {
        private const char TRATTINO = '-';
        private const string DOPPIAFRECCIADX = ">>";
        private const string DOPPIAFRECCIASX = "<<";
        private const string FMP_DATA = "Data: {0}";
        private const string TXT_ListaEccezioni = "Lista eccezioni dalla più interna:";
        private const string FMP_ClassMethod = "Classe: {0} Metodo: {1}";
        private const string FMP_TipoException = "Tipo eccezione: {0}";
        private const string FMP_TipoEccezionePiùEsterna = "Tipo eccezione più esterna: {0}";
        private const string TXT_MessaggiEccezioni = "Messaggi eccezioni:";

        public static string BuildMessage(string pClassName, MethodBase pMethod, Exception ex)
        {
            string trattini = new string(TRATTINO, 80);
            string separator = new string(TRATTINO, 10);
            StringBuilder sb = new StringBuilder(string.Empty);
            sb.Append(DOPPIAFRECCIADX);
            sb.AppendLine(trattini);
            sb.AppendFormat(FMP_DATA, DateTime.Now.ToShortDateString(),
            DateTime.Now.ToLongTimeString());
            sb.AppendLine();
            sb.AppendLine(TXT_ListaEccezioni);
            sb.AppendLine(separator);
            sb.AppendFormat(FMP_ClassMethod, pClassName, pMethod.Name);
            sb.AppendLine();
            sb.AppendLine(separator);
            if (ex.InnerException != null)
            {
                Exception inner = ex.InnerException;
                Stack<string> messageStack = new Stack<string>();
                while (true)
                {
                    messageStack.Push(string.Format(FMP_TipoException, inner.GetType()));
                    messageStack.Push(inner.Message);
                    messageStack.Push(string.Empty);

                    if (inner.InnerException == null)
                    {
                        break;
                    }
                    inner = inner.InnerException;
                } 
                sb.AppendLine(separator);
                sb.AppendLine(TXT_MessaggiEccezioni);
                while (messageStack.Count > 0)
                {
                    sb.AppendLine(messageStack.Pop());
                }
            }
            sb.AppendLine(separator);
            sb.AppendFormat(FMP_TipoEccezionePiùEsterna, ex.GetType());
            sb.AppendLine();
            sb.AppendLine(separator);
            sb.AppendLine(ex.Message);
            sb.Append(DOPPIAFRECCIASX);
            sb.AppendLine(trattini);
            return sb.ToString();
        }
    }
}
Imports System 
Imports System.Collections.Generic 
Imports System.Text 
Imports System.Reflection 

Public NotInheritable Class ExceptionHelper 
    Private Sub New() 
    End Sub 
    Private Const TRATTINO As Char = "-"C 
    Private Const DOPPIAFRECCIADX As String = ">>" 
    Private Const DOPPIAFRECCIASX As String = "<<" 
    Private Const FMP_DATA As String = "Data: {0}" 
    Private Const TXT_ListaEccezioni As String = "Lista eccezioni dalla più interna:" 
    Private Const FMP_ClassMethod As String = "Classe: {0} Metodo: {1}" 
    Private Const FMP_TipoException As String = "Tipo eccezione: {0}" 
    Private Const FMP_TipoEccezionePiùEsterna As String = "Tipo eccezione più esterna: {0}" 
    Private Const TXT_MessaggiEccezioni As String = "Messaggi eccezioni:" 

    Public Shared Function BuildMessage(ByVal pClassName As String, _
    ByVal pMethod As MethodBase, ByVal ex As Exception) As String 
        Dim trattini As New String(TRATTINO, 80) 
        Dim separator As New String(TRATTINO, 10) 
        Dim sb As New StringBuilder(String.Empty) 
        sb.Append(DOPPIAFRECCIADX) 
        sb.AppendLine(trattini) 
        sb.AppendFormat(FMP_DATA, DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString()) 
        sb.AppendLine() 
        sb.AppendLine(TXT_ListaEccezioni) 
        sb.AppendLine(separator) 
        sb.AppendFormat(FMP_ClassMethod, pClassName, pMethod.Name) 
        sb.AppendLine() 
        sb.AppendLine(separator) 
        If ex.InnerException IsNot Nothing Then 
            Dim inner As Exception = ex.InnerException 
            Dim messageStack As New Stack(Of String)() 
            While True 
                messageStack.Push(String.Format(FMP_TipoException, inner.[GetType]())) 
                messageStack.Push(inner.Message) 
                messageStack.Push(String.Empty) 

                If inner.InnerException Is Nothing Then 
                                     Exit While 
                End If 
                inner = inner.InnerException 
            End While 
            sb.AppendLine(separator) 
            sb.AppendLine(TXT_MessaggiEccezioni) 
            While messageStack.Count > 0 
                sb.AppendLine(messageStack.Pop()) 
            End While 
        End If 
        sb.AppendLine(separator) 
        sb.AppendFormat(FMP_TipoEccezionePiùEsterna, ex.[GetType]()) 
        sb.AppendLine() 
        sb.AppendLine(separator) 
        sb.AppendLine(ex.Message) 
        sb.Append(DOPPIAFRECCIASX) 
        sb.AppendLine(trattini) 
        Return sb.ToString() 
    End Function 
End Class

Anche Questa classe è molto semplice anche se potrebbe non sembrarlo, infatti contiene un solo metodo, BuildMessage; questo metodo quando gli vengono passati i seguenti dati:

  • Il nome di una classe,
  • I dati di reflection di un metodo
  • Un’eccezione

Utilizza una funzione ricorsiva per scavare l’eccezione attraverso le sue InnerException e produce uno Stack che contiene la lista delle eccezioni dalla più esterna alla più interna con i relativi messaggi. Terminata la ricorsione, compone un messaggio rileggendo lo Stack e generando una stringa che contiene i dati dell’albero dell’eccezione dal più interno (la prima classe che l’ha intercettata) al più esterno usualmente la user interface ove le eccezioni vengono visualizzate e viene richiesto intervento all’utente per la loro gestione.

Il suo scopo è unicamente quello di fornire un messaggio di base che non deve aiutare l’utente ma il programmatore a comprendere il luogo ove l’eccezione ha avuto origine per cercarla, capire cosa l’ha provocata ed eliminare la causa.

Nel metodo BuildMessage, utilizziamo le classi MethodBase, Exception, Stack, StringBuilder, seguite i link sui loro nomi per saperne di più consultando MSDN.

Conclusioni

Ci manca ancora una classe helper per aver completato il minimo numero di classi base per poter lavorare concretamente su una piccola applicazione, anche se probabilmente, la prima cosa che faremo visto che dobbiamo provare come funzionano queste classi è un articolo sull’uso di NUnit per generare dei test per verificare che quando modifichiamo qualcosa non facciamo danni ad altri punti di un programma. Al termine di quell’ultimo articolo, pubblicherò il progetto della libreria completa per quanto, con un pochino di copia e incolla, se avete letto tutti gli articoli dovreste averla già ricostruita. In questo caso, quando aggiungete le classi, ricordate di porre la DuplicateKeyException sotto la cartella Collections.

Per qualsiasi Feedback, Ulteriore domanda, Chiarimento, oppure se trovate qualche errore usate direttamente il form di contatto con un click sulla bustina in cima alla pagina.

Potete scaricare il progetto esempio dedicato alle classi di uso comune dal link qui sotto indicato.