例外を作成してスローする

例外は、プログラムの実行中にエラーが発生したことを示すために使われます。 エラーを説明する例外オブジェクトが作成された後、throw ステートメントまたは式で ''スロー'' されます。 そのとき、ランタイムは最も互換性のある例外ハンドラーを検索します。

プログラマは、以下の条件が 1 つでも該当するときは、例外をスローする必要があります。

  • メソッドは、定義されている機能を完了できません。 たとえば、メソッドへのパラメーターに無効な値が設定されている場合などです。

    static void CopyObject(SampleClass original)
    {
        _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
    }
    
  • オブジェクトの状態に基づくと、オブジェクトに対して行われた呼び出しが不適切です。 たとえば、読み取り専用ファイルに書き込もうとしたような場合です。 オブジェクトの状態により操作が許可されない場合は、InvalidOperationException のインスタンスまたはこのクラスの派生に基づくオブジェクトをスローします。 次のコードは、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.
        }
    }
    
  • メソッドへの引数が原因で例外が発生しました。 この場合は、元の例外をキャッチして、ArgumentException のインスタンスを作成する必要があります。 元の例外は、InnerException パラメーターとして ArgumentException のコンストラクターに渡す必要があります。

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

    Note

    前の例は、InnerException プロパティの使用方法を示しています。 これは意図的に簡略化されています。 実際には、インデックスを使用する前に、そのインデックスが範囲内にあることをチェックする必要があります。 メンバーを呼び出す前に予測できなかった例外が、パラメーターのメンバーによってスローされたときに、例外をラップするこの手法を使用できます。

例外には、StackTrace というプロパティが含まれています。 この文字列には、現在の呼び出し履歴でのメソッドの名前と、各メソッドの例外がスローされたファイル名と行番号が含まれます。 スタック トレースを開始するポイントから例外をスローする必要があるため、共通言語ランタイム (CLR) によって throw ステートメントのポイントから StackTrace オブジェクトが自動的に作成されます。

すべての例外には、Message というプロパティが含まれています。 例外の原因を説明するには、この文字列を設定する必要があります。 機密性の高い情報はメッセージ テキストに入れないようにする必要があります。 ArgumentException には、Message に加え、例外がスローされる原因となる引数の名前に設定される ParamName というプロパティが含まれています。 プロパティ セッターでは、ParamNamevalue に設定する必要があります。

public と protected メソッドからは、意図された機能を完了できない場合、常に例外がスローされる必要があります。 スローされる例外クラスは、エラー状態に適合する使用可能な例外の中で最も具体的なものになります。 これらの例外はクラスの機能の一部として文書化する必要があり、派生クラスまたは元のクラスの更新では、旧バージョンとの互換性のために同じ動作を維持する必要があります。

例外をスローするときに避ける必要があること

次の一覧は、例外をスローするときに避ける必要があることです。

  • プログラムのフローを変更するために、通常の実行の一部として例外を使用しないでください。 例外はエラー状態の報告と処理のために使用します。
  • スローする代わりに、戻り値またはパラメーターとして例外を返さないでください。
  • 独自のソース コードからは、意図的に System.ExceptionSystem.SystemExceptionSystem.NullReferenceException、または System.IndexOutOfRangeException をスローしないでください。
  • デバッグ モードではスローでき、リリース モードではスローできない例外は、作成しないでください。 開発フェーズ中に実行時エラーを識別するには、代わりにデバッグ アサートを使ってください。

タスクを返すメソッドの例外

async 修飾子で宣言されたメソッドには、例外に関していくつかの特別な考慮事項があります。 async メソッドでスローされた例外は、返されたタスクに格納され、たとえば、タスクが待機されるまで発生しません。 格納されている例外の詳細については、「非同期例外」を参照してください。

メソッドの非同期部分を入力する前に、引数を検証し、ArgumentExceptionArgumentNullException などの対応する例外をスローすることをお勧めします。 つまり、これらの検証例外は、作業が開始される前に同期的に発生する必要があります。 次のコード スニペットは、例外がスローされた場合、ArgumentException 例外は同期的に発生しますが、InvalidOperationException は返されたタスクに格納される例を示しています。

// 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();
    }
}

例外クラスを定義する

プログラムでは、System 名前空間で事前定義された例外クラスをスローするか (上記の場合を除きます)、Exception から派生することで独自の例外クラスを作成することができます。 派生クラスでは、少なくとも 3 つのコンストラクター (パラメーターなしのコンストラクター、メッセージ プロパティを設定するコンストラクター、Message および InnerException プロパティの両方を設定するコンストラクター) を定義する必要があります。 次に例を示します。

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

新しいプロパティによって提供されるデータが例外の解決に役立つ場合は、それを例外クラスに追加します。 派生例外クラスに新しいプロパティを追加する場合は、ToString() をオーバーライドして追加情報を返す必要があります。

C# 言語仕様

詳細については、「C# 言語仕様」の例外throw ステートメントに関するセクションを参照してください。 言語仕様は、C# の構文と使用法に関する信頼性のある情報源です。

関連項目