Непарний синтаксис повернення


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> _():?
Алекс К.

6
@Steve, мені цікаво, якщо ОП має на увазі більше, return _(); IEnumerable<TSource> _()ніж yield return?
Роб

5
Я думаю, що він мав на увазі цю лінію return _(); IEnumerable<TSource> _(). Його можна збентежити тим, що це виглядає, а не фактичним твердженням про повернення.
Матеуш

5
@AkashKava ОП заявила, що існує незвичайна заява про повернення. На жаль, код містить дві заяви про повернення. Тож зрозуміло, якщо люди плутаються, щодо яких він / вона має на увазі.
mjwills

5
Відредагував це питання і ще раз вибач за плутанину.
кускмен

Відповіді:


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(); 

    }

Хитрість полягає в тому, що _ () оголошується після його використання, що цілком чудово.

Пратичне використання локальних функцій

Наведений вище приклад - це лише демонстрація того, як можна використовувати метод inline, але, швидше за все, якщо ви збираєтеся викликати метод лише один раз, тоді він не принесе користі.

Але в наведеному вище прикладі, як згадувалося в коментарях Фосі та Луаана , є перевага використання локальної функції. Оскільки функція з поверненням урожайності не буде виконуватися, якщо хтось її не повторить, у цьому випадку буде виконаний метод поза локальною функцією та перевірка параметрів, навіть якщо ніхто не буде повторювати значення.

Ми багато разів повторювали код у методі, давайте розглянемо цей приклад ..

  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 #.
чі

12
Якщо бути явним, як ніхто, начебто, цього не вказував, цей фрагмент коду використовує локальну функцію, оскільки він є ітератором (відзначте вихід), і тому ліниво виконує. Без локальної функції вам потрібно буде або прийняти, що перевірка вводу відбувається при першому використанні, або мати метод, який коли-небудь буде викликаний одним іншим методом, лежачи навколо з дуже малої причини.
Фоші

6
@ColinM Приклад, який розмістили kuksmen, насправді є однією з головних причин, яка остаточно була реалізована - коли ви робите функцію yield return, код не виконується, доки фактично не перераховується. Це небажано, оскільки ви хочете, наприклад, перевірити аргументи відразу. Єдиний спосіб зробити це в C # - це розділити метод на два методи - один із yield returns, а другий без. Вбудовані методи дозволяють оголосити yieldметод використання всередині , уникаючи безладу та можливого неправильного використання методу, який є суто внутрішнім для його батьківського і не підлягає багаторазовому використанню.
Луань

24

Розглянемо простіший приклад

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

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

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

_() - локальна функція, оголошена в методі, що містить оператор return.


3
Так, я знаю про локальні функції, саме формат мене обдурив ... сподіваюся, це не стане стандартом.
kuskmen

20
Ви маєте на увазі оголошення функції, що починається з того самого рядка? Якщо так, я згоден, це жахливо!
Стюарт

3
Так, це я мав на увазі.
kuskmen

9
За винятком того, що називати це, підкреслення теж жахливо
Icepickle

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