捨棄-c # 基本概念

從 c # 7.0 開始,c # 支援捨棄,也就是在應用程式程式碼中刻意未使用的預留位置變數。 捨棄相當於未指派的變數;它們沒有值。 捨棄會將意圖傳達給編譯器和其他讀取您程式碼的專案:您想要忽略運算式的結果。 您可能想要忽略運算式的結果、元組運算式的一或多個成員、方法的 out 參數,或模式符合運算式的目標。

因為只有一個捨棄變數,所以該變數甚至可能無法配置儲存區。 捨棄可以減少記憶體配置。 捨棄讓程式碼的意圖更清楚。 他們增強了其可讀性和可維護性。

指定變數為 discard 的方式是在其名稱中指派底線 (_)。 例如,下列方法呼叫會傳回一個元組,其中的第一個和第二個值會被捨棄。 area 是先前宣告的變數,設定為所傳回的第三個元件 GetCityInformation

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

從 c # 9.0 開始,您可以使用捨棄來指定 lambda 運算式未使用的輸入參數。 如需詳細資訊,請參閱lambda運算式一文的lambda 運算式章節的輸入參數

_ 是有效的捨棄時,嘗試在指派作業中取出其值或使用它時,會產生編譯器錯誤 cs0301: 「「名稱 ' _ ' 不存在於目前的內容中」。 此錯誤是因為 _ 未指派值,甚至可能尚未指派儲存位置。 如果它是實際的變數,您就無法捨棄一個以上的值,如先前的範例所示。

元組和物件解構

當您的應用程式程式碼使用某些元組專案但忽略其他元組專案時,捨棄會很有用。 例如,下列方法會傳回 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 方法也可讓您從一個物件擷取及解構一組特定的資料。 當您只想要使用一部分的解構值時,可以使用 [捨棄]。 下列範例會將 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 進行的模式比對

捨棄模式 可用於搭配 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。 下列程式碼會使用捨棄來強制執行指派。 當引數為時,指派的右邊會使用 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.
}

下列範例會使用獨立 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

若未將工作指派給捨棄,下列程式碼會產生編譯器警告:

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
    

另請參閱