Udostępnij za pośrednictwem


Tworzenie i zgłaszanie wyjątków

Wyjątki są używane do wskazywania, że wystąpił błąd podczas uruchamiania programu. Obiekty wyjątków opisujące błąd są tworzone, a następnie rzucane za pomocą instrukcji throw lub wyrażenia. Następnie środowisko uruchomieniowe wyszukuje najbardziej zgodną procedurę obsługi wyjątków.

Programiści powinni zgłaszać wyjątki, gdy spełniony jest co najmniej jeden z następujących warunków:

  • Metoda nie może ukończyć zdefiniowanej funkcji. Jeśli na przykład parametr metody ma nieprawidłową wartość:

    static void CopyObject(SampleClass original)
    {
        _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
    }
    
  • Wykonano niewłaściwe wywołanie obiektu na podstawie stanu obiektu. Jednym z przykładów może być próba zapisania do pliku, który jest tylko do odczytu. W przypadkach, gdy stan obiektu nie zezwala na operację, zgłoś wystąpienie InvalidOperationException lub obiektu na podstawie wyprowadzenia tej klasy. Poniższy kod to przykład metody, która zgłasza obiekt InvalidOperationException:

    public class ProgramLog
    {
        FileStream logFile = null!;
        public void OpenLog(FileInfo fileName, FileMode mode) { }
    
        public void WriteLog()
        {
            if (!logFile.CanWrite)
            {
                throw new InvalidOperationException("Logfile cannot be read-only");
            }
            // Else write data to the log and return.
        }
    }
    
  • Gdy argument metody powoduje wyjątek. W takim przypadku oryginalny wyjątek powinien zostać przechwycony, a następnie należy utworzyć wystąpienie ArgumentException. Oryginalny wyjątek powinien być przekazywany do konstruktora ArgumentException jako parametr InnerException.

    static int GetValueFromArray(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (IndexOutOfRangeException e)
        {
            throw new ArgumentOutOfRangeException(
                "Parameter index is out of range.", e);
        }
    }
    

    Notatka

    W poprzednim przykładzie pokazano, jak używać właściwości InnerException. Jest celowo uproszczony. W praktyce przed użyciem należy sprawdzić, czy indeks znajduje się w zakresie. Można użyć tej techniki zawijania wyjątku, gdy element członkowski parametru zgłasza wyjątek, którego nie można przewidzieć przed wywołaniem elementu członkowskiego.

Wyjątki zawierają właściwość o nazwie StackTrace. Ten ciąg zawiera nazwę metod w bieżącym stosie wywołań wraz z nazwą pliku i numerem wiersza, w którym wyjątek został zgłoszony dla każdej metody. Obiekt StackTrace jest tworzony automatycznie przez środowisko uruchomieniowe języka wspólnego (CLR) od punktu instrukcji throw, co oznacza, że wyjątki muszą zostać zgłoszone od momentu rozpoczęcia śledzenia stosu.

Wszystkie wyjątki zawierają właściwość o nazwie Message. Ten ciąg powinien być ustawiony, aby wyjaśnić przyczynę wyjątku. Informacje wrażliwe na zabezpieczenia nie powinny być umieszczane w tekście wiadomości. Oprócz MessageArgumentException zawiera właściwość o nazwie ParamName, która powinna być ustawiona na nazwę argumentu, który spowodował zgłoszenie wyjątku. W ustawieniu właściwości ParamName należy ustawić wartość value.

Metody publiczne i chronione zgłaszają wyjątki, gdy nie mogą ukończyć zamierzonych funkcji. Zgłoszona klasa wyjątku jest najbardziej specyficznym wyjątkiem, który pasuje do warunków błędu. Te wyjątki powinny być udokumentowane jako część funkcjonalności klasy, a klasy pochodne lub aktualizacje oryginalnej klasy powinny zachować to samo zachowanie w celu zapewnienia zgodności z poprzednimi wersjami.

Kwestie, których należy unikać podczas zgłaszania wyjątków

Poniższa lista zawiera rozwiązania, których należy unikać podczas zgłaszania wyjątków:

  • Nie używaj wyjątków, aby zmienić przepływ programu w ramach zwykłego wykonywania. Użyj wyjątków, aby zgłaszać i obsługiwać sytuacje błędów.
  • Wyjątki nie powinny być zwracane jako wartość lub parametr, lecz powinny być zgłaszane.
  • Nie rzucaj celowo System.Exception, System.SystemException, System.NullReferenceExceptionani System.IndexOutOfRangeException z własnego kodu źródłowego.
  • Nie twórz wyjątków, które mogą być zgłaszane w trybie debugowania, ale nie w trybie wydania. Aby zidentyfikować błędy czasu wykonywania w fazie programowania, zamiast tego użyj Debug.Assert.

Wyjątki w metodach zwracających zadania

Metody zadeklarowane za pomocą modyfikatora async mają pewne specjalne kwestie, jeśli chodzi o wyjątki. Wyjątki zgłoszone w metodzie async są przechowywane w zwracanym zadaniu i nie pojawiają się, dopóki na przykład zadanie nie zostanie oczekiwane. Aby uzyskać więcej informacji na temat przechowywanych wyjątków, zobacz wyjątki asynchroniczne.

Zalecamy zweryfikowanie argumentów i zgłoszenie odpowiednich wyjątków, takich jak ArgumentException i ArgumentNullException, przed wprowadzeniem asynchronicznych części metod. Oznacza to, że te wyjątki weryfikacji powinny pojawić się synchronicznie przed rozpoczęciem pracy. Poniższy fragment kodu przedstawia przykład, w którym, jeśli wyjątki zostaną zgłoszone, wyjątki ArgumentException pojawią się synchronicznie, podczas gdy wyjątki InvalidOperationException będą przechowywane w zwróconym zadaniu.

// Non-async, task-returning method.
// Within this method (but outside of the local function),
// any thrown exceptions emerge synchronously.
public static Task<Toast> ToastBreadAsync(int slices, int toastTime)
{
    if (slices is < 1 or > 4)
    {
        throw new ArgumentException(
            "You must specify between 1 and 4 slices of bread.",
            nameof(slices));
    }

    if (toastTime < 1)
    {
        throw new ArgumentException(
            "Toast time is too short.", nameof(toastTime));
    }

    return ToastBreadAsyncCore(slices, toastTime);

    // Local async function.
    // Within this function, any thrown exceptions are stored in the task.
    static async Task<Toast> ToastBreadAsyncCore(int slices, int time)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Console.WriteLine("Putting a slice of bread in the toaster");
        }
        // Start toasting.
        await Task.Delay(time);

        if (time > 2_000)
        {
            throw new InvalidOperationException("The toaster is on fire!");
        }

        Console.WriteLine("Toast is ready!");

        return new Toast();
    }
}

Definiowanie klas wyjątków

Programy mogą zgłaszać wstępnie zdefiniowaną klasę wyjątków w przestrzeni nazw System (z wyjątkiem przypadków uprzednio wymienionych), lub tworzyć własne klasy wyjątków, dziedzicząc z Exception. Klasy pochodne powinny definiować co najmniej trzy konstruktory: jeden konstruktor bez parametrów, jeden, który ustawia właściwość komunikatu, i taki, który ustawia zarówno właściwości Message, jak i InnerException. Na przykład:

[Serializable]
public class InvalidDepartmentException : Exception
{
    public InvalidDepartmentException() : base() { }
    public InvalidDepartmentException(string message) : base(message) { }
    public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { }
}

Dodaj nowe właściwości do klasy wyjątków, gdy podane dane są przydatne do rozpoznawania wyjątku. Jeśli nowe właściwości zostaną dodane do klasy wyjątku pochodnego, ToString() powinien zostać przesłonięty, aby zwrócić dodane informacje.

Specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz Wyjątki i Instrukcję throw w Specyfikacji języka C#. Specyfikacja języka jest ostatecznym źródłem składni i użycia języka C#.

Zobacz też