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 SubEnd ClassPer 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 SubEnd ClassLa 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 SubEnd ClassLa 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 SubEnd ClassEnd NamespaceLa 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 SubPrivate 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 StringDim 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 ThenDim 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 ThenExit WhileEnd If
inner = inner.InnerException
End While
sb.AppendLine(separator)
sb.AppendLine(TXT_MessaggiEccezioni)
While messageStack.Count > 0
sb.AppendLine(messageStack.Pop())
End WhileEnd 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 FunctionEnd ClassAnche 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 dei commenti in calce a questo articolo oppure scrivete all'autrice . |