Пустые переменные — руководство по языку C#Discards - C# Guide

Начиная с версии 7.0 язык C# поддерживает пустые переменные. Это временные фиктивные переменные, которые намеренно не используются в коде приложения.Starting with C# 7.0, C# supports discards, which are temporary, dummy variables that are intentionally unused in application code. Пустые переменные эквивалентны переменным, которым не присвоены значения; пустые переменные не имеют значений.Discards are equivalent to unassigned variables; they do not have a value. Так как пустая переменная по сути всего одна и этой переменной может даже не выделяться память, пустые переменные помогают расходовать память более экономно.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.

Чтобы использовать пустую переменную, назначьте ей в качестве имени символ подчеркивания (_).You indicate that a variable is a discard by assigning it the underscore (_) as its name. Например, следующий вызов метода возвращает трехкомпонентный кортеж, в котором первое и второе значения являются пустыми переменными, а 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 пустые переменные поддерживаются в присваиваниях в следующих контекстах:In C# 7.0, discards are supported in assignments in the following contexts:

  • Деконструкция кортежей и объектов.Tuple and object deconstruction.
  • Сопоставление шаблонов с операторами is и switch.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

Пустые переменные особенно удобны при работе с кортежами, когда код вашего приложения использует одни элементы кортежа и игнорирует другие.Discards are particularly useful in working with tuples when your application code uses some tuple elements but ignores others. Например, следующий метод QueryCityDataForYears возвращает кортеж из шести элементов: название города, его площадь, год, численность населения города в этом году, другой год и численность населения города в том году.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. Следовательно, нас интересуют только два значения численности населения, которые хранятся в кортеже. Остальные значения можно обработать как пустые переменные.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

Подробнее о деконструкции кортежей с помощью пустых переменных: Деконструкция кортежей и других типов.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. Пустые переменные можно использовать, когда вы хотите работать только с подмножеством деконструируемых значений.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()
    {
        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!

Подробнее о деконструкции пользовательских типов с помощью пустых переменных: Деконструкция кортежей и других типов.For more information on deconstructing user-defined types with discards, see Deconstructing tuples and other types.

Сопоставление шаблонов с операторами switch и isPattern matching with switch and is

Шаблон пустой переменной можно использовать в сопоставлении шаблонов с ключевыми словами is и switch.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 и не является ли он null.The 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. В примере также используется шаблон пустой переменной для обработки объектов любого другого типа с определенным значением.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), который определяет, является ли строковое представление даты допустимым для текущего языка и региональных параметров.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 метода является пустым.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

Отдельная пустая переменнаяA standalone discard

Вы можете использовать отдельную пустую переменную, чтобы указать, что переменную необходимо игнорировать.You can use a standalone discard to indicate any variable that you choose to ignore. В следующем примере отдельная пустая переменная используется, чтобы игнорировать объект 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. При использовании вне поддерживаемого контекста _ считается не пустой, а действительной переменной.When used outside of a supported context, _ is treated not as a discard but as a valid variable. Если в области уже есть идентификатор с именем _, использование _ в качестве отдельной пустой переменной может привести к следующим результатам:If an identifier named _ is already in scope, the use of _ as a standalone discard can result in:

  • Случайное изменение значения переменной _ в области из-за присвоения ей значения пустой переменной.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