Excepții personalizate în C# (.NET)

O clasă proprie derivată din Exception descrie o eroare din domeniul aplicației și poate transporta date suplimentare.

Sintaxă C#
class StocInsuficientException(string produs) : Exception($"Stoc insuficient: {produs}") { }

Ce face Excepții personalizate

Când nicio excepție din .NET nu descrie bine problema (stoc insuficient, comandă deja plătită), creează o clasă care moștenește Exception. Numele se termină prin convenție în Exception, iar codul care apelează o poate prinde separat cu catch (StocInsuficientException ex).

Pune în excepție datele utile celui care o tratează (produsul, cantitatea cerută) ca proprietăți, nu doar în mesaj. Când transformi o excepție de nivel jos într-una de domeniu, dă-o pe cea originală ca innerException, ca să nu pierzi cauza.

Ghidul .NET recomandă trei constructori: fără parametri, cu mesaj și cu mesaj plus excepție interioară. Constructorul de serializare (SerializationInfo) este învechit din .NET 8 și nu mai trebuie scris.

Exemple

Exemplul 1: Excepție cu date suplimentare

var stoc = new Dictionary<string, int> { ["laptop"] = 2 };

try
{
    Comanda(stoc, "laptop", 5);
}
catch (StocInsuficientException ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine($"Disponibil: {ex.Disponibil}");
}

static void Comanda(Dictionary<string, int> stoc, string produs, int cantitate)
{
    int disponibil = stoc.GetValueOrDefault(produs);
    if (cantitate > disponibil)
        throw new StocInsuficientException(produs, cantitate, disponibil);
    stoc[produs] = disponibil - cantitate;
}

class StocInsuficientException(string produs, int cerut, int disponibil)
    : Exception($"Stoc insuficient pentru {produs}: cerut {cerut}, disponibil {disponibil}.")
{
    public string Produs { get; } = produs;
    public int Disponibil { get; } = disponibil;
}

Rezultat

Stoc insuficient pentru laptop: cerut 5, disponibil 2.
Disponibil: 2

Exemplul 2: Excepție interioară (InnerException)

Eroarea de format devine o excepție de domeniu, dar cauza rămâne disponibilă în InnerException.

try
{
    CitesteConfig("port=abc");
}
catch (ConfigException ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.InnerException?.GetType().Name);
}

static int CitesteConfig(string linie)
{
    try
    {
        return int.Parse(linie.Split('=')[1]);
    }
    catch (FormatException ex)
    {
        throw new ConfigException($"Valoare invalidă în '{linie}'", ex);
    }
}

class ConfigException : Exception
{
    public ConfigException() { }
    public ConfigException(string message) : base(message) { }
    public ConfigException(string message, Exception inner) : base(message, inner) { }
}

Rezultat

Valoare invalidă în 'port=abc'
FormatException

Greșeli frecvente

  • Moștenirea din ApplicationException: nu aduce nimic în plus, ghidurile .NET recomandă direct Exception.
  • Aruncarea unei excepții noi fără innerException pierde cauza reală a erorii.
  • Câte o clasă de excepție pentru fiecare mesaj posibil. Creează o excepție nouă doar dacă cineva o va prinde separat.

Comenzi înrudite

  • throw și throw expressions: Aruncă o excepție care oprește execuția normală; throw; simplu re-aruncă excepția prinsă, păstrând locul unde a apărut.
  • try / finally: Blocul finally rulează mereu la ieșirea din try: după succes, după o excepție prinsă sau neprinsă și după return.
  • Moștenire: virtual, override și base: O clasă derivată preia membrii clasei de bază (class B : A); metodele virtual pot fi înlocuite cu override, iar base apelează varianta de bază.