C # 7: подчеркивание (_) и звезда (*) в переменной Out


79

Я читал о новых функциях переменных в C # 7 здесь . У меня два вопроса:

  1. Это говорит

    Мы также разрешаем "отбрасывать" как параметры out в форме a _, чтобы вы могли игнорировать параметры, которые вам не нужны:

    p.GetCoordinates(out var x, out _); // I only care about x
    

    В: Думаю, это всего лишь информация, а не новая функция C # 7, потому что мы можем сделать это и в версии до C # 7.0:

    var _;
    if (Int.TryParse(str, out _))
    ...
    

    или мне что-то здесь не хватает?

  2. Мой код выдает ошибку, когда я делаю, как указано в том же блоге:

    ~Person() => names.TryRemove(id, out *);
    

    *не является допустимым идентификатором. Я полагаю, это недосмотр Мадса Торгерсена?


15
in out _ _не является переменной, вы не объявляете ее и не можете использовать по имени. В int _том , что это переменная.
Evk

9
Подстановочный знак звездочки, похоже, не вошел в финальную версию C # 7.
Майкл Штум

3
@NikhilAgrawal, но это другой синтаксис. В своем вопросе вы используете out _без var. С varним это действительно так же , как и раньше.
Evk

2
@NikhilAgrawal, который действительно компилируется по какой-то странной причине. Однако если посмотреть на декомпилированный код - этого назначения просто нет, оно полностью удалено. И если вы сделаете что-то подобное Console.WriteLine(_)- это не будет компилироваться, утверждая, что такой переменной нет. Довольно странно. Более того: если вы сделаете что-то подобное _ = SomeMethodCall()- это будет заменено просто SomeMethodCall()в скомпилированном коде. В конце концов, вы по-прежнему не можете использовать эту переменную в каком-либо значимом смысле.
Evk

Ответы:


111

Discards в C # 7 может использоваться везде, где объявлена ​​переменная, чтобы - как следует из названия - отбросить результат. Таким образом, сброс можно использовать без переменных:

p.GetCoordinates(out var x, out _);

и его можно использовать для отбрасывания результата выражения:

_ = 42;

В этом примере

p.GetCoordinates(out var x, out _);
_ = 42;

Переменная _,, не вводится. Есть всего два случая использования сброса.

Если, однако, идентификатор _существует в области действия, то сбросы использовать нельзя:

var _ = 42;
_ = "hello"; // error - a string cannot explicitly convert from string to int

Исключение составляют случаи, когда _переменная используется как выходная переменная. В этом случае компилятор игнорирует тип или varи рассматривает его как сброс:

if (p.GetCoordinates(out double x, out double _))
{
    _ = "hello"; // works fine.
    Console.WriteLine(_); // error: _ doesn't exist in this context.
}

Обратите внимание, что это происходит только в том случае, если в данном случае используется out var _или out double _. Просто используйте, out _а затем он будет рассматриваться как ссылка на существующую переменную _, если она находится в области видимости, например:

string _;
int.TryParse("1", out _); // complains _ is of the wrong type

Наконец, эта *нотация была предложена на ранних этапах обсуждения отбрасывания, но от_ нее отказались в пользу того, что последняя является более широко используемой нотацией в других языках .


Я думаю, вы имеете в виду '... из- _за того, что последний является ...'
martijnn2008

@ martijnn2008, замечательно. Благодарю.
Дэвид Арно

1
Я предполагаю, что это подразумевается, но смысл отбрасывания в том, что его потенциальное значение никогда не сохраняется?
Sinjai

Утверждение, что _ = 42«отбросить [s] результат выражения» вводит в заблуждение, потому что _ = 42само по себе является выражением со значением 42, поэтому фактического отбрасывания не происходит. По-прежнему есть разница, потому что _ = 42;это тоже утверждение, а 42;не это, что имеет значение в некоторых контекстах.
Jeroen Mostert

1
Формулировка хороша, просто она _ = 42не может показать, в чем смысл этого отбрасывания - то есть, когда вам нужно «не сохранять» выражение, но все равно оценивать его, поскольку вы обычно можете оценить (нетривиальный , полезно), без сохранения. Я сам не могу сразу придумать полезный пример (и я не знаю, есть ли он или это просто результат согласованности в грамматике).
Jeroen Mostert

30

Другой пример оператора Discard _в C # 7 - сопоставление с шаблоном переменной типа objectв switchоператоре, который недавно был добавлен в C # 7:

Код:

static void Main(string[] args)
{
    object x = 6.4; 
    switch (x)
    {
        case string _:
            Console.WriteLine("it is string");
            break;
        case double _:
            Console.WriteLine("it is double");
            break;
        case int _:
            Console.WriteLine("it is int");
            break;
        default:
            Console.WriteLine("it is Unknown type");
            break;
    }

    // end of main method
}

Этот код будет соответствовать типу и отбрасывать переменную, переданную в case ... _.


14

Для более любопытных

Рассмотрим следующий фрагмент

static void Main(string[] args)
{
    //....
    int a;
    int b;

    Test(out a, out b);
    Test(out _, out _);    
    //....
}

private static void Test(out int a, out int b)
{
    //...
}

Вот что происходит:

...

13:             int  a;
14:             int  b;
15: 
16:             Test(out a, out b);
02340473  lea         ecx,[ebp-40h]  
02340476  lea         edx,[ebp-44h]  
02340479  call        02340040  
0234047E  nop  
    17:             Test(out _, out _);
0234047F  lea         ecx,[ebp-48h]  
02340482  lea         edx,[ebp-4Ch]  
02340485  call        02340040  
0234048A  nop 

...

Как вы можете видеть за сценой, два звонка делают одно и то же.

Как отметил @ Servé Laurijssen, круто то, что вам не нужно предварительно объявлять переменные, что удобно, если вас не интересуют некоторые значения.


3
IL должен быть таким же, потому что вызываемой функции по-прежнему требуются слоты для выходных переменных. Просто использование нового синтаксиса отбрасывания позволяет компилятору делать дальнейшие предположения о локальной переменной (или, скорее, об отсутствии), позволяя ему более эффективно использовать ее (по крайней мере, теоретически; я не знаю, есть ли уже какие-либо оптимизации. в компиляторе на данный момент).
совать

9

По поводу первого вопроса

Я предполагаю, что это всего лишь информация, а не новая функция C # 7, потому что мы можем сделать это и до C # 7.0.

var _;
if (Int.TryParse(str, out _))
    // ...

Новинка в том, что вам больше не нужно объявлять _внутри или снаружи выражения, и вы можете просто ввести

int.TryParse(s, out _);

Попробуйте сделать это одним лайнером до C # 7:

private void btnDialogOk_Click_1(object sender, RoutedEventArgs e)
{
     DialogResult = int.TryParse(Answer, out _);
}

7
Чтобы добавить: подчеркивание очень хорошо работает для методов с несколькими выходными параметрами, например, SomeMethod(out _, out _, out three)имеет 3 выходных параметра, но я отбрасываю первые два без необходимости создавать такие переменные, как unused1, unused2и т. Д.
Майкл Штум

@MichaelStum: Что здесь происходит? if (SomeMethod(out _, out _, out _)) _ = 5; К _чему это относится?
Nikhil Agrawal

4
@NikhilAgrawal Переменной не было бы _вообще, даже если бы вы использовали out var _. Кажется, что подчеркивание сделано специально, чтобы отбросить результат.
Майкл Штум

0

В C # 7.0 (Visual Studio 2017 примерно в марте 2017 г.) сбросы поддерживаются в назначениях в следующих контекстах:


Другие полезные заметки

  • сброс может уменьшить выделение памяти. Поскольку они проясняют цель вашего кода, они улучшают его читабельность и ремонтопригодность.
  • Обратите внимание, что _ также является допустимым идентификатором. При использовании вне поддерживаемого контекста

Простой пример: здесь мы не хотим использовать 1-й и 2-й параметры, а нам нужен только 3-й параметр.

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

Расширенный пример в корпусе переключателя, который также использовал современное сопоставление шаблонов корпуса переключателя ( источник )

switch (exception)                {
case ExceptionCustom exceptionCustom:       
        //do something unique
        //...
    break;
case OperationCanceledException _:
    //do something else here and we can also cast it 
    //...
    break;
default:
    logger?.Error(exception.Message, exception);
    //..
    break;

}


0

В: ... мы можем сделать это и в версии до C # 7.0:

var _;
if (Int.TryParse(str, out _))

или мне что-то здесь не хватает?

Это не одно и то же.
Ваш код выполняет задание.

В C # 7.0 _ не является переменной, он сообщает компилятору отбросить значение
( если вы не объявили _ как переменную ... если вы это сделаете, переменная используется вместо символа сброса)

Пример: вы можете использовать _ как sting и int в одной строке кода :

string a; 
int b;
Test(out a, out b);
Test(out _, out _);

//...

void Test(out string a, out int b)
{
   //...
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.