Undantagshanteringsuttryck – throw, try-catch, try-finallyoch try-catch-finally

Du använder throw instruktionerna och try för att arbeta med undantag. Använd -instruktionen throw för att utlösa ett undantag. Använd -instruktionen try för att fånga och hantera undantag som kan inträffa under körningen av ett kodblock.

Instruktionen throw

Instruktionen throw utlöser ett undantag:

if (shapeAmount <= 0)
{
    throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}

I en throw e; instruktion måste uttryckets e resultat implicit konverteras till System.Exception.

Du kan använda de inbyggda undantagsklasserna, till exempel ArgumentOutOfRangeException eller InvalidOperationException. .NET tillhandahåller också hjälpmetoder för att utlösa undantag under vissa villkor: ArgumentNullException.ThrowIfNull och ArgumentException.ThrowIfNullOrEmpty. Du kan också definiera dina egna undantagsklasser som härleds från System.Exception. Mer information finns i Skapa och utlösa undantag.

I ett catch block kan du använda en throw; -instruktion för att återskapa undantaget som hanteras av catch blocket:

try
{
    ProcessShapes(shapeAmount);
}
catch (Exception e)
{
    LogError(e, "Shape processing failed.");
    throw;
}

Kommentar

throw; bevarar den ursprungliga stackspårningen av undantaget, som lagras i Exception.StackTrace egenskapen. I motsats till det throw e; uppdaterar egenskapen eför StackTrace .

När ett undantag utlöses letar CLR (Common Language Runtime) efter det catch block som kan hantera det här undantaget. Om den metod som körs för närvarande inte innehåller något catch sådant block tittar CLR på metoden som anropade den aktuella metoden och så vidare upp i anropsstacken. Om inget catch block hittas avslutar CLR den körande tråden. Mer information finns i avsnittet Hur undantag hanteras i C#-språkspecifikationen.

Uttrycket throw

Du kan också använda throw som ett uttryck. Detta kan vara praktiskt i ett antal fall, bland annat:

  • villkorsoperatorn. I följande exempel används ett throw uttryck för att utlösa en ArgumentException när den skickade matrisen args är tom:

    string first = args.Length >= 1 
        ? args[0]
        : throw new ArgumentException("Please supply at least one argument.");
    
  • operatorn null-coalescing. I följande exempel används ett throw uttryck för att utlösa en ArgumentNullException när strängen som ska tilldelas till en egenskap är null:

    public string Name
    {
        get => name;
        set => name = value ??
            throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
    }
    
  • en uttrycksfyllig lambda eller metod. I följande exempel används ett throw uttryck för att utlösa ett InvalidCastException för att indikera att en konvertering till ett DateTime värde inte stöds:

    DateTime ToDateTime(IFormatProvider provider) =>
             throw new InvalidCastException("Conversion to a DateTime is not supported.");
    

Instruktionen try

Du kan använda -instruktionen try i något av följande formulär: try-catch - för att hantera undantag som kan inträffa under körningen av koden i ett try block, try-finally - för att ange den kod som körs när kontrollen lämnar try blocket och try-catch-finally - som en kombination av de föregående två formulären.

Instruktionen try-catch

Använd -instruktionen try-catch för att hantera undantag som kan inträffa under körningen av ett kodblock. Placera koden där ett undantag kan inträffa i ett try block. Använd en catch-sats för att ange den bastyp av undantag som du vill hantera i motsvarande catch block:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

Du kan ange flera catch-satser:

try
{
    var result = await ProcessAsync(-3, 4, cancellationToken);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Processing is cancelled.");
}

När ett undantag inträffar granskas catch-satser i den angivna ordningen, uppifrån och ned. Maximalt körs endast ett catch block för undantag som genereras. Som föregående exempel också visar kan du utelämna deklarationen av en undantagsvariabel och endast ange undantagstypen i en catch-sats. En catch-sats utan någon angiven undantagstyp matchar alla undantag och måste, om det finns, vara den sista catch-satsen.

Om du vill återskapa ett undantag som fångas använder du -instruktionenthrow, som följande exempel visar:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
    LogError(e, "Processing failed.");
    throw;
}

Kommentar

throw; bevarar den ursprungliga stackspårningen av undantaget, som lagras i Exception.StackTrace egenskapen. I motsats till det throw e; uppdaterar egenskapen eför StackTrace .

Ett when undantagsfilter

Tillsammans med en undantagstyp kan du också ange ett undantagsfilter som ytterligare undersöker ett undantag och avgör om motsvarande catch block hanterar undantaget. Ett undantagsfilter är ett booleskt uttryck som följer nyckelordet when , vilket visas i följande exempel:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e) when (e is ArgumentException || e is DivideByZeroException)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

I föregående exempel används ett undantagsfilter för att tillhandahålla ett enda catch block för att hantera undantag av två angivna typer.

Du kan ange flera catch satser för samma undantagstyp om de skiljer sig från undantagsfilter. En av dessa satser kanske inte har något undantagsfilter. Om en sådan sats finns måste den vara den sista av de satser som anger den undantagstypen.

Om en catch sats har ett undantagsfilter kan den ange den undantagstyp som är samma som eller mindre härledd än en undantagstyp för en catch sats som visas efter den. Om det till exempel finns ett undantagsfilter behöver en catch (Exception e) sats inte vara den sista satsen.

Undantag i asynkrona metoder och iteratormetoder

Om ett undantag inträffar i en asynkron funktion sprids det till funktionens anropare när du väntar på resultatet av funktionen, som följande exempel visar:

public static async Task Run()
{
    try
    {
        Task<int> processing = ProcessAsync(-1);
        Console.WriteLine("Launched processing.");

        int result = await processing;
        Console.WriteLine($"Result: {result}.");
    }
    catch (ArgumentException e)
    {
        Console.WriteLine($"Processing failed: {e.Message}");
    }
    // Output:
    // Launched processing.
    // Processing failed: Input must be non-negative. (Parameter 'input')
}

private static async Task<int> ProcessAsync(int input)
{
    if (input < 0)
    {
        throw new ArgumentOutOfRangeException(nameof(input), "Input must be non-negative.");
    }

    await Task.Delay(500);
    return input;
}

Om ett undantag inträffar i en iteratormetod sprids det endast till anroparen när iteratorn avancerar till nästa element.

Instruktionen try-finally

I en try-finally instruktion finally körs blocket när kontrollen lämnar try blocket. Kontrollen kan lämna try blocket på grund av

  • normal körning,
  • körning av en jump-instruktion (dvs. , breakreturn, continueeller goto) eller
  • spridning av ett undantag från try blocket.

I följande exempel används finally blocket för att återställa tillståndet för ett objekt innan kontrollen lämnar metoden:

public async Task HandleRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    finally
    {
        Busy = false;
    }
}

Du kan också använda finally blocket för att rensa allokerade resurser som används i try blocket.

Kommentar

När typen av en resurs implementerar gränssnittet eller bör du överväga -instruktionenusing.IAsyncDisposableIDisposable -instruktionen using säkerställer att förvärvade resurser tas bort när kontrollen lämnar -instruktionen using . Kompilatorn omvandlar en using instruktion till en try-finally -instruktion.

I nästan alla fall finally körs block. De enda fall där finally block inte körs omfattar omedelbar avslutning av ett program. En sådan avslutning kan till exempel inträffa på grund av anropet Environment.FailFast eller ett OverflowException undantag InvalidProgramException . De flesta operativsystem utför en rimlig resursrensning som en del av att stoppa och ta bort processen.

Instruktionen try-catch-finally

Du använder en try-catch-finally instruktion både för att hantera undantag som kan inträffa under körningen try av blocket och ange den kod som måste köras när kontrollen lämnar instruktionen try :

public async Task ProcessRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    catch (Exception e) when (e is not OperationCanceledException)
    {
        LogError(e, $"Failed to process request for item ID {itemId}.");
        throw;
    }
    finally
    {
        Busy = false;
    }

}

När ett undantag hanteras av ett catch block finally körs blocket efter körningen av blocket catch (även om ett annat undantag inträffar under körningen catch av blocket). Information om catch och block finns i instruktionen try-catch respektive instruktionsavsnittentry-finally.finally

Språkspecifikation för C#

Mer information finns i följande avsnitt i C#-språkspecifikationen:

Se även