Discard - C# 指南Discards - C# Guide

從 C# 7.0 開始,C# 支援 discard,這是應用程式程式碼中刻意未使用的暫存虛擬變數。Starting with C# 7.0, C# supports discards, which are temporary, dummy variables that are intentionally unused in application code. Discard 相當於未指派的變數,不具有任何值。Discards are equivalent to unassigned variables; they do not have a value. 因為只有一個 discard 變數,而且該變數可能甚至未配置儲存空間,所以 discard 可減少記憶體配置。Because there is only a single discard variable, and that variable may not even be allocated storage, discards can reduce memory allocations. 這些變數讓您的程式碼意圖更清楚,因而提高其可讀性和可維護性。Because they make the intent of your code clear, they enhance its readability and maintainability.

指定變數為 discard 的方式是在其名稱中指派底線 (_)。You indicate that a variable is a discard by assigning it the underscore (_) as its name. 例如,下列方法呼叫會傳回 3 Tuple,其中第一個和第二個值會被捨棄,而area 是先前宣告的變數,要設定為 GetCityInformation 所傳回的第三個對應元件:For example, the following method call returns a 3-tuple in which the first and second values are discards and area is a previously declared variable to be set to the corresponding third component returned by GetCityInformation:

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

在 C# 7.0 中,下列內容中的指派支援 discard:In C# 7.0, discards are supported in assignments in the following contexts:

  • 元組和物件解構Tuple and object deconstruction.
  • isswitch 進行的模式比對。Pattern matching with is and switch.
  • out 參數進行的方法呼叫。Calls to methods with out parameters.
  • 範圍內沒有 _ 時的獨立 _A standalone _ when no _ is in scope.

_ 是有效的捨棄時,嘗試擷取其值或用於指派作業會產生編譯器錯誤 CS0301:「目前內容中不存在名稱 '_'」。When _ is a valid discard, attempting to retrieve its value or use it in an assignment operation generates compiler error CS0301, "The name '_' does not exist in the current context". 這是因為 _ 未指派值,可能甚至未指派儲存位置。This is because _ is not assigned a value, and may not even be assigned a storage location. 如果它原本是實際變數,可能無法像上述範例一樣捨棄多個值。If it were an actual variable, you could not discard more than one value, as the previous example did.

元組和物件解構Tuple and object deconstruction

當您的應用程式程式碼使用一些元組項目但忽略其他項目時,discard 特別適合用來處理元組。Discards are particularly useful in working with tuples when your application code uses some tuple elements but ignores others. 例如,下列 QueryCityDataForYears 方法會傳回 6 元組,其中包含城市名稱、其區碼、年份、該年的城市人口、次年的年份、次年的城市人口。For example, the following QueryCityDataForYears method returns a 6-tuple with the name of a city, its area, a year, the city's population for that year, a second year, and the city's population for that second year. 此範例會顯示這兩年之間的人口變化。The example shows the change in population between those two years. 在元組可用的資料中,我們對城市區碼不感興趣,並在設計階段得知城市名稱及兩個日期。Of the data available from the tuple, we're unconcerned with the city area, and we know the city name and the two dates at design-time. 因此,我們只對元組中所儲存的兩個人口值感興趣,而可以將其餘值視為 discard。As a result, we're only interested in the two population values stored in the tuple, and can handle its remaining values as discards.

using System;
using System.Collections.Generic;

public class Example
{
   public static void Main()
   {
       var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

       Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
   }
   
   private 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 解構元組的詳細資訊,請參閱解構元組和其他類型For more information on deconstructing tuples with discards, see Deconstructing tuples and other types.

類別、結構或介面的 Deconstruct 方法也可讓您從一個物件擷取及解構一組特定的資料。The Deconstruct method of a class, structure, or interface also allows you to retrieve and deconstruct a specific set of data from an object. 如果您只想使用部分已解構的值,可以使用 discard。You can use discards when you are interested in working with only a subset of the deconstructed values. 下列範例會將 Person 物件解構為四個字串 (名字、姓氏、城市和州/省),但捨棄姓氏和州/省。The following example deconstructs a Person object into four strings (the first and last names, the city, and the state), but discards the last name and the state.

using System;

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;
   }                           
}

public class Example
{
   public static void Main()
   {
       Person p = new Person("John", "Quincy", "Adams", "Boston", "MA");

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

如需使用 discard 解構使用者定義型別的詳細資訊,請參閱解構元組和其他類型For more information on deconstructing user-defined types with discards, see Deconstructing tuples and other types.

switchis 進行的模式比對Pattern matching with switch and is

「捨棄模式」可用於以 isswitch 關鍵字進行的模式比對。The discard pattern can be used in pattern matching with the is and switch keywords. 每個運算式一律會比對捨棄模式。Every expression always matches the discard pattern.

下列範例會定義 ProvidesFormatInfo 方法,該方法使用 is 陳述式來判斷物件是否提供 IFormatProvider 實作,並測試物件是否為 nullThe following example defines a ProvidesFormatInfo method that uses is statements to determine whether an object provides an IFormatProvider implementation and tests whether the object is null. 它也會使用捨棄模式來處理任何其他類型的非 Null 物件。It also uses the discard pattern to handle non-null objects of any other type.

using System;
using System.Globalization;

public class Example
{
   public static void Main()
   {
      object[] objects = { CultureInfo.CurrentCulture, 
                           CultureInfo.CurrentCulture.DateTimeFormat, 
                           CultureInfo.CurrentCulture.NumberFormat,
                           new ArgumentException(), null };
      foreach (var obj in objects)
         ProvidesFormatInfo(obj);
   }

   private static void ProvidesFormatInfo(object obj)         
   {
      switch (obj)
      {
         case IFormatProvider fmt:
            Console.WriteLine($"{fmt} object");
            break;
         case null:
            Console.Write("A null object reference: ");
            Console.WriteLine("Its use could result in a NullReferenceException");
            break;
         case object _:
            Console.WriteLine("Some object type without format information");
            break;
      }
   }
}
// The example displays the following output:
//    en-US 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 參數進行的方法呼叫Calls to methods with out parameters

當呼叫 Deconstruct 方法解構使用者定義型別 (類別、結構或介面的執行個體) 時,您可以捨棄個別 out 引數的值。When calling the Deconstruct method to deconstruct a user-defined type (an instance of a class, structure, or interface), you can discard the values of individual out arguments. 但當使用 out 參數呼叫任何方法時,您也可以捨棄 out 引數的值。But you can also discard the value of out arguments when calling any method with an out parameter.

下列範例會呼叫 DateTime.TryParse(String, out DateTime) 方法,以判斷日期的字串表示在目前的文化特性 (culture) 中是否有效。The following example calls the DateTime.TryParse(String, out DateTime) method to determine whether the string representation of a date is valid in the current culture. 因為此範例只需要驗證日期字串,而不需要將它剖析以擷取日期,所以該方法的 out 引數為 discard。Because the example is concerned only with validating the date string and not with parsing it to extract the date, the out argument to the method is a discard.

using System;

public class Example
{
   public static void Main()
   {
      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

獨立 discardA standalone discard

您可以使用獨立 discard,指出您選擇忽略的任何變數。You can use a standalone discard to indicate any variable that you choose to ignore. 下列範例會使用獨立 discard 來忽略非同步作業傳回的 Task 物件。The following example uses a standalone discard to ignore the Task object returned by an asynchronous operation. 這會造成在作業即將完成時,隱藏作業所擲回的例外狀況。This has the effect of suppressing the exception that the operation throws as it is about to complete.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      ExecuteAsyncMethods().Wait();
   }

   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

請注意,_ 也是有效的識別項。Note that _ is also a valid identifier. 在支援的內容之外使用時,_ 會視為有效的變數,而不是 discard。When used outside of a supported context, _ is treated not as a discard but as a valid variable. 如果範圍內已有名為 _ 的識別項,使用 _ 作為獨立 discard 可能會導致:If an identifier named _ is already in scope, the use of _ as a standalone discard can result in:

  • 將預定的 dscard 值指派給範圍內的 _ 變數,而意外修改變數的值。Accidental modification of the value of the in-scope _ variable by assigning it the value of the intended discard. 例如:For example:

    private static void ShowValue(int _)
    {
       byte[] arr = { 0, 0, 1, 2 };
       _ = BitConverter.ToInt32(arr, 0);
       Console.WriteLine(_);
    }
    // The example displays the following output:
    //       33619968
    
  • 違反型別安全的編譯器錯誤。A compiler error for violating type safety. 例如:For example:

    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:「無法在此範圍宣告名為 '_' 的區域變數或參數,因為該名稱已用於封入區域變數範圍,以定義區域變數或參數」。Compiler 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." 例如:For example:

    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   
    

另請參閱See also