次の方法で共有


破棄 - C# の基礎

破棄は、アプリケーション コードで意図的に使用されないプレースホルダー変数です。 破棄は、未割り当ての変数と同等です。つまり、値がありません。 破棄は、コンパイラやコードを読み取る他のユーザーに意図を伝えます。式の結果を無視することを意図しました。 式の結果、タプル式の 1 つ以上のメンバー、メソッドへの out パラメーター、またはパターン マッチング式のターゲットを無視できます。

破棄すると、コードの意図が明確になります。 破棄は、コードが変数を使用しないことを示します。 読みやすさと保守容易性を高めます。

変数が破棄であることを示す場合は、名前としてアンダースコア (_) を割り当てます。 たとえば、次のメソッド呼び出しは、1 番目と 2 番目の値が破棄されるタプルを返します。 area は、 GetCityInformationによって返される 3 番目のコンポーネントに設定された、以前に宣言された変数です。

(_, _, area) = city.GetCityInformation(cityName);

「破棄」と言う機能を使用して、ラムダ式の未使用の入力パラメーターを示すことができます。 詳細については、「ラムダ式」の記事の「ラムダ式の入力パラメーター」セクションを参照してください。

_が有効な破棄である場合、その値を取得しようとしたり、割り当て操作で使用しようとすると、コンパイラ エラー CS0103 "名前 '_' は現在のコンテキストに存在しません" が生成されます。 このエラーは、 _ に値が割り当てられず、ストレージの場所も割り当てられない可能性があるためです。 実際の変数の場合は、前の例のように、複数の値を破棄できませんでした。

タプルとオブジェクトの分解

分解は、複数のタプルがあり、アプリケーション コードで一部のタプル要素を使用し、その他の要素を無視する場合に便利です。 たとえば、次の QueryCityDataForYears メソッドは、市区町村の名前、その地域、年、その年の市区町村の人口、2 年目の市区町村の人口を持つタプルを返します。 この例は、2 つの年の間に変化した人口数を示しています。 タプルから使用できるデータのうち、市区町村の地域は使用しません。また、指定時に市区町村名と 2 つの日付はわかっています。 そのため、タプルに格納されている 2 つの人口値のみが必要であり、残りの値は破棄対象として処理できます。

var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");

static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
    int population1 = 0, population2 = 0;
    double area = 0;

    if (name == "New York City")
    {
        area = 468.48;
        if (year1 == 1960)
        {
            population1 = 7781984;
        }
        if (year2 == 2010)
        {
            population2 = 8175133;
        }
        return (name, area, year1, population1, year2, population2);
    }

    return ("", 0, 0, 0, 0, 0);
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149

破棄を使用したタプルの分解の詳細については、「タプルとその他の型の分解」を参照してください。

クラス、構造体、またはインターフェイスの Deconstruct メソッドを使用すると、オブジェクトから特定のデータ セットを取得および分解することもできます。 分解された値の一部のみが必要な場合は、破棄を使用できます。 次の例では、 Person オブジェクトを 4 つの文字列 (姓、市区町村、州) に分解しますが、姓と状態は破棄します。

using System;

namespace Discards
{
    public class Person
    {
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public string City { get; set; }
        public string State { get; set; }

        public Person(string fname, string mname, string lname,
                      string cityName, string stateName)
        {
            FirstName = fname;
            MiddleName = mname;
            LastName = lname;
            City = cityName;
            State = stateName;
        }

        // Return the first and last name.
        public void Deconstruct(out string fname, out string lname)
        {
            fname = FirstName;
            lname = LastName;
        }

        public void Deconstruct(out string fname, out string mname, out string lname)
        {
            fname = FirstName;
            mname = MiddleName;
            lname = LastName;
        }

        public void Deconstruct(out string fname, out string lname,
                                out string city, out string state)
        {
            fname = FirstName;
            lname = LastName;
            city = City;
            state = State;
        }
    }
    class Example
    {
        public static void Main()
        {
            var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

            // Deconstruct the person object.
            var (fName, _, city, _) = p;
            Console.WriteLine($"Hello {fName} of {city}!");
            // The example displays the following output:
            //      Hello John of Boston!
        }
    }
}

破棄を使用したユーザー定義型の分解の詳細については、「タプルとその他の型の分解」を参照してください。

switch を使用したパターン マッチング

破棄パターンは、パターン マッチングでswitch 式と共に使用できます。 nullを含むすべての式は、常に破棄パターンと一致します。

次の例では、ProvidesFormatInfo式を使用して、オブジェクトがswitch実装を提供するかどうかを判断し、オブジェクトがIFormatProviderされているかどうかをテストするnull メソッドを定義します。 また、破棄パターンを使用して、他の型の null 以外のオブジェクトを処理します。

object?[] objects = [CultureInfo.CurrentCulture,
                   CultureInfo.CurrentCulture.DateTimeFormat,
                   CultureInfo.CurrentCulture.NumberFormat,
                   new ArgumentException(), null];
foreach (var obj in objects)
    ProvidesFormatInfo(obj);

static void ProvidesFormatInfo(object? obj) =>
    Console.WriteLine(obj switch
    {
        IFormatProvider fmt => $"{fmt.GetType()} object",
        null => "A null object reference: Its use could result in a NullReferenceException",
        _ => "Some object type without format information"
    });
// The example displays the following output:
//    System.Globalization.CultureInfo object
//    System.Globalization.DateTimeFormatInfo object
//    System.Globalization.NumberFormatInfo object
//    Some object type without format information
//    A null object reference: Its use could result in a NullReferenceException

out パラメーターを持つメソッドの呼び出し

Deconstruct メソッドを呼び出してユーザー定義型 (クラス、構造体、またはインターフェイスのインスタンス) を分解する場合は、個々のout引数の値を破棄できます。 ただし、out パラメーターを使用して任意のメソッドを呼び出すときに、out引数の値を破棄することもできます。

次の例では、 DateTime.TryParse(String, out DateTime) メソッドを呼び出して、日付の文字列表現が現在のカルチャで有効かどうかを判断します。 この例は日付文字列の検証のみに関係しており、日付を抽出するための解析には関係しないため、メソッドの out 引数は破棄です。

string[] dateStrings = ["05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8",
                      "2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
                      "5/01/2018 14:57:32.80 -07:00",
                      "1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM",
                      "Fri, 15 May 2018 20:10:57 GMT"];
foreach (string dateString in dateStrings)
{
    if (DateTime.TryParse(dateString, out _))
        Console.WriteLine($"'{dateString}': valid");
    else
        Console.WriteLine($"'{dateString}': invalid");
}
// The example displays output like the following:
//       '05/01/2018 14:57:32.8': valid
//       '2018-05-01 14:57:32.8': valid
//       '2018-05-01T14:57:32.8375298-04:00': valid
//       '5/01/2018': valid
//       '5/01/2018 14:57:32.80 -07:00': valid
//       '1 May 2018 2:57:32.8 PM': valid
//       '16-05-2018 1:00:32 PM': invalid
//       'Fri, 15 May 2018 20:10:57 GMT': invalid

スタンドアロンの破棄

スタンドアロンの破棄を使用して、無視する変数を指定できます。 一般的な用途の 1 つは、引数が null でないことを保証するために割り当てを使用することです。 次のコードでは、破棄を使用して割り当てを強制しています。 代入の右側ではnull合体演算子を使用して、引数がSystem.ArgumentNullExceptionのときにnullをスローします。 コードは割り当ての結果を必要としないため、破棄されます。 式は null チェックを強制します。 破棄することで意図が明確になるため、割り当ての結果は使用されません。

public static void Method(string arg)
{
    _ = arg ?? throw new ArgumentNullException(paramName: nameof(arg), message: "arg can't be null");

    // Do work with arg.
}

次の例では、スタンドアロン破棄を使用して、非同期操作によって返される Task オブジェクトを無視します。 タスクの割り当ての結果、この処理が完了するときにスローされる例外が抑制される効果があります。 意図が明確になります。 Taskを破棄し、その非同期操作から生成されたエラーを無視します。

private static async Task ExecuteAsyncMethods()
{
    Console.WriteLine("About to launch a task...");
    _ = Task.Run(() =>
    {
        var iterations = 0;
        for (int ctr = 0; ctr < int.MaxValue; ctr++)
            iterations++;
        Console.WriteLine("Completed looping operation...");
        throw new InvalidOperationException();
    });
    await Task.Delay(5000);
    Console.WriteLine("Exiting after 5 second delay");
}
// The example displays output like the following:
//       About to launch a task...
//       Completed looping operation...
//       Exiting after 5 second delay

破棄にタスクを割り当てずに、次のコードはコンパイラ警告を生成します。

private static async Task ExecuteAsyncMethods()
{
    Console.WriteLine("About to launch a task...");
    // CS4014: Because this call is not awaited, execution of the current method continues before the call is completed.
    // Consider applying the 'await' operator to the result of the call.
    Task.Run(() =>
    {
        var iterations = 0;
        for (int ctr = 0; ctr < int.MaxValue; ctr++)
            iterations++;
        Console.WriteLine("Completed looping operation...");
        throw new InvalidOperationException();
    });
    await Task.Delay(5000);
    Console.WriteLine("Exiting after 5 second delay");

デバッガーを使用して上記の 2 つのサンプルのいずれかを実行した場合、デバッガーは例外がスローされたときにプログラムを停止します。 デバッガーがアタッチされていない場合、どちらの場合も例外は自動的に無視されます。

_ も有効な識別子です。 サポートされているコンテキストの外部で使用すると、 _ は破棄としてではなく、有効な変数として扱われます。 _という名前の識別子が既にスコープ内にある場合、スタンドアロンの破棄として_を使用すると、次の結果が得られます。

  • スコープ内の_変数の値を、意図した破棄の値を割り当てることで誤って変更してしまう。 例えば次が挙げられます。
    private static void ShowValue(int _)
    {
       byte[] arr = [0, 0, 1, 2];
       _ = BitConverter.ToInt32(arr, 0);
       Console.WriteLine(_);
    }
     // The example displays the following output:
     //       33619968
    
  • 型の安全性に違反した場合のコンパイラ エラー。 例えば次が挙げられます。
    private static bool RoundTrips(int _)
    {
       string value = _.ToString();
       int newValue = 0;
       _ = Int32.TryParse(value, out newValue);
       return _ == newValue;
    }
    // The example displays the following compiler error:
    //      error CS0029: Cannot implicitly convert type 'bool' to 'int'
    

こちらも参照ください