破棄 - C# の基礎
破棄は、アプリケーション コードで意図的に使用されないプレースホルダー変数です。 破棄は、未割り当ての変数と同等です。つまり、値がありません。 破棄によって、あなたのコードを読み取るコンパイラおよびその他のユーザーに対して、次のような意図が伝わります。あなたは式の結果を無視するつもりでした。 あなたは、式の結果、タプル式の 1 つ以上のメンバー、メソッドの out
パラメーター、またはパターン マッチング式のターゲットを無視したい可能性があります。
破棄することで、コードの意図が明確になります。 破棄するとは、コードでその変数が使用されないことを意味します。 そのため、読みやすさと保守容易性が向上します。
変数を破棄と指定するには、変数名にアンダースコア (_
) を指定します。 たとえば、次のメソッド呼び出しでタプルが返され、1 つ目と 2 つ目の値は破棄です。 area
は、GetCityInformation
によって返される 3 つ目のコンポーネントに設定された、以前に宣言された変数です。
(_, _, area) = city.GetCityInformation(cityName);
破棄を使用して、ラムダ式の未使用の入力パラメーターを指定できます。 詳細については、ラムダ式に関する記事の「ラムダ式の入力パラメーター」セクションを参照してください。
_
が有効な破棄の場合、その値を取得しようとすると、または代入演算で使用しようとすると、"名前 '_' は、現在のコンテキストに存在していません" というコンパイラ エラー CS0103 が生成されます。 このエラーの原因は、_
に値が割り当てられておらず、記憶域の場所も割り当てることができないことです。 実際の変数であれば、前の例のように、複数の値を破棄できません。
タプルとオブジェクトの分解
分解は、複数のタプルがあり、アプリケーション コードで一部のタプル要素を使用し、その他の要素を無視する場合に便利です。 たとえば、次の QueryCityDataForYears
メソッドは、市区町村名、その地域、年、市区町村のその年の人口、2 つ目の年、市区町村のその 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
も含め、あらゆる式は常に破棄パターンと一致します。
switch
式を使用して、オブジェクトが IFormatProvider 実装を提供しているかどうかを判断し、オブジェクトが null
かどうかをテストする ProvidesFormatInfo
メソッドの定義例を次に示します。 また、破棄パターンを使用して、その他の任意の型の 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 合体演算子が使用され、引数が null
の場合に System.ArgumentNullException がスローされます。 このコードに割り当ての結果は不要なため、破棄されます。 式によって 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'
- コンパイラ エラー CS0136 "ローカルまたはパラメーター '_' は、その名前が外側のローカルのスコープでローカルやパラメーターの定義に使用されているため、このスコープでは宣言できません" が発生する。次に例を示します。
public void DoSomething(int _) { var _ = GetValue(); // Error: cannot declare local _ when one is already in scope } // The example displays the following compiler error: // error CS0136: // A local or parameter named '_' cannot be declared in this scope // because that name is used in an enclosing local scope // to define a local or parameter
関連項目
.NET