棄元 - C# 指南


從 C# 7.0 開始,C# 支持棄元,這是一種在應用程序代碼中人為取消使用的占位符變量。 棄元相當於未賦值的變量;它們沒有值。 因為只有一個棄元變量,甚至不為該變量分配存儲空間,所以棄元可減少內存分配。 因為它們使代碼的意圖清楚,增強了其可讀性和可維護性。

通過將下划線 (_) 賦給一個變量作為其變量名,指示該變量為一個占位符變量。 例如,下面的方法調用返回 3 元組,其中的第一個和第二個值是棄元,area 是一個以前聲明的變量,它將設置為 GetCityInformation 返回的相應的第三個部分:

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

在 C# 7.0 及更高版本中,支持在以下上下文的分配中使用棄元:

  • 元組和對象析構
  • 使用 is 和 switch 的模式匹配。
  • 對具有 out 參數的方法的調用。
  • 當范圍內沒有 _ 時,獨立的 _

從 C# 9.0 開始,可以使用棄元指定 Lambda 表達式中不使用的輸入參數。 有關詳細信息,請參閱 Lambda 表達式一文中的 Lambda 表達式的輸入參數一節。

當 _ 是有效占位符時,嘗試檢索其值或在賦值操作中使用它時會生成編譯器錯誤 CS0301:當前上下文中不存在名稱 "_"。 這是因為 _ 未賦值,甚至可能未分配存儲位置。 如果它是一個實際變量,則不能像之前的示例那樣對多個值使用占位符。

元組和對象析構

如果應用程序代碼使用某些元組元素,但忽略其他元素,這時使用占位符來處理元組就會特別有用。 例如,以下的 QueryCityDataForYears 方法返回一個 6 元組,包含城市名稱、城市面積、一個年份、該年份的城市人口、另一個年份及該年份的城市人口。 該示例顯示了兩個年份之間人口的變化。 對於元組提供的數據,我們不關注城市面積,並在一開始就知道城市名稱和兩個日期。 因此,我們只關注存儲在元組中的兩個人口數量值,可將其余值作為占位符處理。

 
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

有關使用占位符析構元組的詳細信息,請參閱 析構元組和其他類型

類、結構或接口的 Deconstruct 方法還允許從對象中檢索和析構一組特定的數據。 如果想只使用析構值的一個子集時,可使用棄元。 以下示例將 Person 對象析構為四個字符串(名字、姓氏、城市和省/市/自治區),但舍棄姓氏和省/市/自治區。

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()
    {
        var 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!

有關使用棄元析構用戶定義的類型的詳細信息,請參閱 析構元組和其他類型

使用 switch 和 is 的模式匹配

棄元模式可通過 is 和 switch 關鍵字用於模式匹配。 每個表達式始終匹配棄元模式。

以下示例定義了一個 ProvidesFormatInfo 方法,該方法使用 is 語句來確定對象是否提供 IFormatProvider 實現並測試對象是否為 null。 它還使用占位符模式來處理任何其他類型的非 null 對象。

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 參數調用方法

當調用 Deconstruct 方法來析構用戶定義類型(類、結構或接口的實例)時,可使用占位符表示單個 out 參數的值。 但當使用 out 參數調用任何方法時,也可使用占位符表示 out 參數的值。

以下示例調用 DateTime.TryParse(String, out DateTime) 方法來確定日期的字符串表示形式在當前區域性中是否有效。 因為該示例側重驗證日期字符串,而不是解析它來提取日期,所以方法的 out 參數為占位符。

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

獨立棄元

可使用獨立棄元來指示要忽略的任何變量。 以下示例使用獨立占位符來忽略異步操作返回的 Task 對象。 這一操作的效果等同於抑制操作即將完成時所引發的異常。

using System;
using System.Threading.Tasks;

public class Example
{
   public static async Task Main(string[] args)
   {
      await ExecuteAsyncMethods();
   }

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

      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM