Оператор синтаксиса нечетного возврата


106

Я знаю, это может показаться странным, но я даже не знаю, как искать этот синтаксис в Интернете, а также не уверен, что именно означает.

Итак, я просмотрел код MoreLINQ, а потом заметил этот метод

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));

    return _(); IEnumerable<TSource> _()
    {
        var knownKeys = new HashSet<TKey>(comparer);
        foreach (var element in source)
        {
            if (knownKeys.Add(keySelector(element)))
                yield return element;
        }
    }
}

Что это за странное заявление о возврате? return _();?


6
Или вы имеете в виду return _(); IEnumerable<TSource> _():?
Alex K.

6
@Steve, интересно, имеет ли OP больше, return _(); IEnumerable<TSource> _()чем yield return?
Роб

5
Я думаю, он имел в виду эту строчку return _(); IEnumerable<TSource> _(). Его могло смутить то, как это выглядит, а не фактическое выражение return.
Mateusz

5
@AkashKava ОП сказал, что было странное заявление о возврате. К сожалению, код содержит два оператора возврата. Так что понятно, если люди не понимают, о чем они говорят.
mjwills

5
Отредактировал вопрос, и еще раз прошу прощения за недоразумение.
kuskmen

Ответы:


106

Это C # 7.0, который поддерживает локальные функции ....

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
       this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
        if (source == null) throw new 
           ArgumentNullException(nameof(source));
        if (keySelector == null) throw 
             new ArgumentNullException(nameof(keySelector));

        // This is basically executing _LocalFunction()
        return _LocalFunction(); 

        // This is a new inline method, 
        // return within this is only within scope of
        // this method
        IEnumerable<TSource> _LocalFunction()
        {
            var knownKeys = new HashSet<TKey>(comparer);
            foreach (var element in source)
            {
                if (knownKeys.Add(keySelector(element)))
                    yield return element;
            }
        }
    }

Текущий C # с Func<T>

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
       this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
        if (source == null) throw new 
           ArgumentNullException(nameof(source));
        if (keySelector == null) throw 
             new ArgumentNullException(nameof(keySelector));

        Func<IEnumerable<TSource>> func = () => {
            var knownKeys = new HashSet<TKey>(comparer);
            foreach (var element in source)
            {
                if (knownKeys.Add(keySelector(element)))
                    yield return element;
            }
       };

        // This is basically executing func
        return func(); 

    }

Хитрость в том, что _ () объявляется после использования, что совершенно нормально.

Практическое использование локальных функций

Приведенный выше пример - это просто демонстрация того, как можно использовать встроенный метод, но, скорее всего, если вы собираетесь вызвать метод только один раз, он бесполезен.

Но в приведенном выше примере, как упоминалось в комментариях Phoshi и Luaan , есть преимущество использования локальной функции. Поскольку функция с yield return не будет выполняться, если кто-то не выполнит ее итерацию, в этом случае будет выполняться метод вне локальной функции, и будет выполняться проверка параметров, даже если никто не будет повторять значение.

Мы много раз повторяем код в методе, давайте посмотрим на этот пример ..

  public void ValidateCustomer(Customer customer){

      if( string.IsNullOrEmpty( customer.FirstName )){
           string error = "Firstname cannot be empty";
           customer.ValidationErrors.Add(error);
           ErrorLogger.Log(error);
           throw new ValidationError(error);
      }

      if( string.IsNullOrEmpty( customer.LastName )){
           string error = "Lastname cannot be empty";
           customer.ValidationErrors.Add(error);
           ErrorLogger.Log(error);
           throw new ValidationError(error);
      }

      ... on  and on... 
  }

Я мог бы оптимизировать это с помощью ...

  public void ValidateCustomer(Customer customer){

      void _validate(string value, string error){
           if(!string.IsNullOrWhitespace(value)){

              // i can easily reference customer here
              customer.ValidationErrors.Add(error);

              ErrorLogger.Log(error);
              throw new ValidationError(error);                   
           }
      }

      _validate(customer.FirstName, "Firstname cannot be empty");
      _validate(customer.LastName, "Lastname cannot be empty");
      ... on  and on... 
  }

4
@ZoharPeled Ну .. опубликованный код действительно показывает использование этой функции .. :)
Роб

2
@ColinM Одно из преимуществ заключается в том, что анонимная функция может легко получить доступ к переменным со своего «хоста».
mjwills

6
Вы уверены, что в языке C # это на самом деле называется анонимной функцией? Кажется, у него есть имя, а именно _AnonymousFunctionили просто _, в то время как я ожидал, что настоящая анонимная функция будет чем-то вроде (x,y) => x+y. Я бы назвал это локальной функцией, но я не привык к терминологии C #.
chi

12
Чтобы быть точным, поскольку, похоже, никто не указал на это, этот фрагмент кода использует локальную функцию, потому что это итератор (обратите внимание на yield), и поэтому выполняется лениво. Без локальной функции вам нужно было бы либо согласиться с тем, что проверка ввода происходит при первом использовании, либо иметь метод, который будет вызываться только одним другим методом, лежащим без особых причин.
Phoshi

6
@ColinM Опубликованный пример куксмена на самом деле является одной из основных причин, по которой это было наконец реализовано - когда вы создаете функцию с yield return, код не выполняется до тех пор, пока перечисляемое не будет фактически перечислено. Это нежелательно, так как вы хотите, например, сразу же проверить аргументы. Единственный способ сделать это в C # - разделить метод на два метода: один с yield returns, а другой без. Встроенные методы позволяют вам объявлять yieldиспользуемый метод внутри , избегая беспорядка и потенциального неправильного использования метода, который является строго внутренним для своего родителя и не может использоваться повторно.
Luaan

24

Рассмотрим более простой пример

void Main()
{
    Console.WriteLine(Foo()); // Prints 5
}

public static int Foo()
{
    return _();

    // declare the body of _()
    int _()
    {
        return 5;
    }
}

_() - это локальная функция, объявленная внутри метода, содержащего оператор возврата.


3
Да, я знаю, что насчет локальных функций меня обмануло форматирование ... надеюсь, это не станет стандартом.
kuskmen

20
Вы имеете в виду объявление функции, начинающееся в той же строке? Если так, согласен, это ужасно!
Стюарт

3
Да, это я имел в виду.
kuskmen

9
Если не считать этого наименования подчеркивания, тоже ужасно
Icepickle

1
@AkashKava: вопрос не в том, легален ли C #, а в том, легко ли понять код (и, следовательно, легко поддерживать и приятно читать) при таком форматировании. Личные предпочтения играют роль, но я склонен согласиться со Стюартом.
PJTraill
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.