Discard - C# 基礎
Discard 是應用程式程式碼中刻意未使用的預留位置變數。 Discard 相當於未指派的變數,不具有任何值。 Dscard 會與編譯器和其他讀取程式碼的意圖通訊:您想要忽略運算式的結果。 您可能想要忽略運算式的結果、Tuple 運算式的一或多個成員、方法的 out
參數,或模式比對運算式的目標。
Dsicard 讓程式碼的意圖更為清楚。 Dsicard 表示我們的程式碼永遠不會使用該變數。 可增強其可讀性和可維護性。
指定變數為 discard 的方式是在其名稱中指派底線 (_
)。 例如,下列方法呼叫會傳回元組,其中的第一個和第二個值為 Discard。 area
是先前宣告的變數,設定為 GetCityInformation
所傳回的第三個元件:
(_, _, area) = city.GetCityInformation(cityName);
您可以使用 Discards 來指定 Lambda 運算式未使用的輸入參數。 如需詳細資訊,請參閱 Lambda 運算式一文的 Lambda 運算式輸入參數一節。
當 _
是有效的 discard 時,嘗試擷取其值或用於指派作業會產生編譯器錯誤 CS0103:「目前內容中不存在名稱 '_'」。 此錯誤是因為 _
未指派值,可能甚至未指派儲存位置。 如果它原本是實際變數,可能無法像上述範例一樣捨棄多個值。
元組和物件解構
當您的應用程式程式碼使用一些元組項目但忽略其他項目時,discard 適合用來處理元組。 例如,下列 QueryCityDataForYears
方法會傳回元組,其中包含城市名稱、其區碼、年份、該年的城市人口、次年的年份、次年的城市人口。 此範例會顯示這兩年之間的人口變化。 在元組可用的資料中,我們對城市區碼不感興趣,並在設計階段得知城市名稱及兩個日期。 因此,我們只對元組中所儲存的兩個人口值感興趣,而可以將其餘值視為 discard。
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
如需使用 discard 解構元組的詳細資訊,請參閱解構元組和其他類型。
類別、結構或介面的 Deconstruct
方法也可讓您從一個物件擷取及解構一組特定的資料。 如果您只想使用部分已解構的值,可以使用 discard。 下列範例會將 Person
物件解構為四個字串 (名字、姓氏、城市和州/省),但捨棄姓氏和州/省。
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!
}
}
}
如需使用 discard 解構使用者定義型別的詳細資訊,請參閱解構元組和其他類型。
以 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) 方法,以判斷日期的字串表示在目前的文化特性 (culture) 中是否有效。 因為此範例只需要驗證日期字串,而不需要將它剖析以擷取日期,所以該方法的 out
引數為 discard。
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
獨立 discard
您可以使用獨立 discard,指出您選擇忽略的任何變數。 一個典型的用法是使用指派來確保引數不是 Null。 下列程式碼會使用 discard 來強制指派。 指派的右側會使用Null 聯合運算子,在引數為 null
時擲回 System.ArgumentNullException。 程式碼不需要指派的結果,因此會被捨棄。 運算式會強制執行 Null 檢查。 Discard 會釐清您的意圖:不需要或使用指派的結果。
public static void Method(string arg)
{
_ = arg ?? throw new ArgumentNullException(paramName: nameof(arg), message: "arg can't be null");
// Do work with arg.
}
下列範例會使用獨立 discard 來忽略非同步作業傳回的 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
若未將工作指派給 discard,下列程式碼會產生編譯器警告:
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");
注意
如果您使用偵錯工具執行上述兩個範例之一,偵錯工具會在擲回例外狀況時停止程式。 如果沒有附加偵錯工具,這兩種情況下都會以無訊息方式忽略例外狀況。
_
也是有效的識別項。 在支援的內容之外使用時,_
會視為有效的變數,而不是 discard。 如果範圍內已有名為 _
的識別項,使用 _
作為獨立 discard 可能會導致:
- 將預定的 dscard 值指派給範圍內的
_
變數,而意外修改變數的值。 例如: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