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

17

Я прочитал ответ на вопрос о Stack Overflow, содержащий следующий предложенный код:

Action<Exception> logAndEat = ex => 
{  
    // Log Error and eat it
};

try
{
    // Call to a WebService
}
catch (SoapException ex)
{
    logAndEat(ex);
}
catch (HttpException ex)
{
    logAndEat(ex);
}
catch (WebException ex)
{
    logAndEat(ex);
}

Мой вопрос: в чем преимущество (если есть) использования выражения лямбда для LogAndEat в отличие от (по моему мнению, более простого и более очевидного) частного метода следующим образом:

private void LogAndEat(Exception ex)
{
    // Log Error and eat it
}

Изменить: Спасибо за ответы до сих пор, но лишь немного уточнил мой фундаментальный вопрос: какой подход лучше / вы бы порекомендовали в этом случае? Выражение лямбда или частный метод?

    
задан Ben Robbins 20.08.2010 в 06:29
источник

7 ответов

1

Спасибо всем за отличные ответы, которые я проголосовал, но я подумал, что я подытожу их, чтобы попытаться захватить плюсы и минусы в одном ответе.

Плюсы использования выражения лямбда (LE) вместо частного метода:

  • A LE привязан к методу, в котором он объявлен, поэтому, если он используется только этим методом, тогда намерение выражается явным выражением лямбда (даже если можно исключить делегат LE, все же можно утверждать, что цель объявления LE в методе заключается в том, что LE привязан к методу). То есть, будучи явным с точки зрения ожидаемого использования.
  • Лямбда-выражения ведут себя как замыкания, поэтому они могут обращаться к переменным, ограниченным методом, в котором они объявлены. Это может быть более аккуратным, чем передача большого количества параметров частному методу.
  • Переменные, захваченные LE, в противном случае были бы параметрами для частного метода, и это можно использовать, чтобы разрешить форму currying.

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

  • Поскольку LE может обращаться к переменным, привязанным к методу, в котором они содержатся, во время отладки невозможно изменить код в вызывающем методе.

Существует также более субъективная проблема ремонтопригодности, и можно утверждать, что LE не так хорошо понимается большинством разработчиков как частный метод и, следовательно, несколько менее ремонтируема. Можно также утверждать, что LE улучшает ремонтопригодность, поскольку он инкапсулирован в метод, в котором он вызывается, а не частный метод, который видим для всего класса.

    
ответ дан Ben Robbins 26.08.2010 в 09:00
источник
12

Я думаю, вы могли бы подумать о выражении лямбда в этом примере как о немногочисленном вложенных функциях Pascal в что это код, который может быть выполнен только по методу, в котором он был объявлен.

Частный метод можно вызывать из любого метода в том же классе, тогда как лямбда, подобная этому, является локальной для текущего метода и поэтому явно используется только в этом контексте.

Это действительно единственное преимущество, о котором я могу думать - явное с точки зрения ожидаемого использования.

    
ответ дан Matt Hamilton 20.08.2010 в 06:34
источник
  • Хотя, конечно, вы можете передать делегата, если хотите. Существует никаких ограничений, которые мешают вам выполнять код вне метода объявления, если экземпляр делегата будет удален. –  Eric Lippert 20.08.2010 в 06:45
  • @ Эрик, да, вы всегда можете вернуть лямбду точно. Кажется, что не было того, что примерный код собирался сделать, поэтому я отказался от этой возможности. :) –  Matt Hamilton 20.08.2010 в 06:48
  • Таким образом, это то, что противодействует тяге вашего ответа, или можно спорить (как мне кажется), что ваш ответ верен, если вы рассматриваете предполагаемую область действия выражения, а не фактическую возможную область действия, если делегат экземпляр передан? Ваш ответ имеет смысл для меня, но использование лямбда-выражения, похожего на это, кажется мне для меня немного сложным - в том, что это не так, это просто что-то необычное для предельной выгоды. –  Ben Robbins 20.08.2010 в 07:38
  • Да, мой аргумент сохраняется, если вы не возвращаете лямбду из метода (или назначаете его частному полю или что-то еще). –  Matt Hamilton 20.08.2010 в 08:14
  • Я написал код в исходном вопросе, и в этом случае Мэтт прав, почему я написал его таким образом. Нечего было подразумевать, что функциональность «log and eat» была повторно использована (в противном случае я предполагаю, что это был вызов метода в вопросе), поэтому я просто продемонстрировал один способ инкапсулировать общий код внутри границ функции без утечки какой-либо реализации детали вне функции. Конечно, если код был полезным и многоразовым, тогда было бы более целесообразно включить его в функцию, а не в делегат. –  Greg Beech 20.08.2010 в 11:29
9

Переменные, захваченные logAndEat , в противном случае были бы параметрами метода LogAndEat . Вы можете считать это формой каррирования.

    
ответ дан Bryan Watts 20.08.2010 в 06:33
источник
  • +1, хотя этого не происходит в данном конкретном примере кода. Это одна из веских причин сделать это. –  Matt Hamilton 20.08.2010 в 06:35
  • +1 все о сфере –  Darko Z 20.08.2010 в 07:02
  • Действительно ли это форма каррирования? –  crypted 20.08.2010 в 07:07
  • Bryan. Как я понимаю, currying разбивает функцию / метод, который принимает несколько параметров таким образом, что метод можно вызывать, используя последовательность функций с одним аргументом. Пожалуйста, включите свет, если я ошибаюсь? –  crypted 20.08.2010 в 07:13
  • @ Int3: каждая захваченная переменная концептуально является параметром метода. Закрытие предоставляет ссылку, поэтому ее можно удалить из явного списка параметров; Однако намерение выглядит очень похоже на традиционное g (x) = f (x, 2) currying. –  Bryan Watts 20.08.2010 в 07:15
Показать остальные комментарии
3

LogAndEat может ссылаться на частные поля внутри функции, где она определена. Итак:

private bool caughtException; 

Action<Exception> logAndEat = ex => 
    {  
        caughtException = true;
    };

try
{
    // Call to a WebService
}
catch (SoapException ex)
{
    logAndEat(ex);
}
catch (HttpException ex)
{
    logAndEat(ex);
}
catch (WebException ex)
{
    logAndEat(ex);
}

if (caughtException)
{
    Console.Writeline("Ma, I caught an exception!");
}

Это банальный пример (!), но это потенциально может быть намного более аккуратным, чем передача набора параметров с помощью частного метода.     

ответ дан Jeremy McGee 20.08.2010 в 08:03
источник
  • И общедоступные поля в классе также называются «переменными». Нет причин не использовать язык для эффективной передачи точки, которая в этом случае должна была напомнить читателю, что «переменная» обычно не будет доступна с помощью кода, определенного вне функции. –  shannon 02.04.2013 в 14:13
2

Чтобы понять, лучше ли использовать лямбда-выражение (как это было в ) или использовать« правильный »метод, нам необходимо принять решение, основанное на том, как многократно используется общая обработка ошибок код.

  • Если общий код обработки ошибок полностью повторно используется другими функциями в разных классах приложения, то он, вероятно, должен быть внутренней или публичной функцией, которая может быть в другом классе, если это делает организацию кода более разумно.

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

  • Если общий код обработки ошибок не может быть повторно использован какой-либо другой функцией и должен использоваться только этой конкретной функцией, тогда у вас есть два варианта. Первый заключается в том, чтобы использовать делегат для инкапсуляции его в пределах границ функции, гарантируя, что функция не будет утечка каких-либо деталей реализации. Вторая заключается в использовании частной функции в том же классе с комментарием, что ее следует вызывать только функцией, для которой она предназначена.

В третьем случае нет четкого «наилучшего способа» для этого, поскольку оба являются совершенно законными способами достижения одной и той же цели. Моя тенденция состояла бы в том, чтобы использовать делегат, когда общий код мал и / или вам нужно какое-то поведение делегатов, таких как захват переменных.

Теперь, возвращаясь к вопросу, почему я написал код с помощью делегата? У меня не было информации о том, был ли код обработки ошибок повторно использован, поэтому я предположил, что это не так, и решил использовать делегата, поскольку я решил, что людям будет легко перейти от этого к пониманию того, что он может быть «правильным», метод (как и у вас), тогда как если бы я показал использование «правильной» функции, люди не могли понять, что делегат будет альтернативой.

    
ответ дан Greg Beech 20.08.2010 в 11:49
источник
1

Многие приходят к личным предпочтениям, никто не может сказать, что один абсолютный путь - это правильный путь или неправильный путь.

Lambda Expressions ведут себя как замыкания на других языках, о чем они говорят, что они могут обращаться к переменным, ограниченным методом, в котором они объявлены. Это добавляет много гибкости в то, что вы можете сделать в своем коде, но приходит за счет того, что во время отладки не удалось изменить код в этом методе.

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

    
ответ дан Kevin McKelvin 20.08.2010 в 07:45
источник
0

IMO Мне не нравятся крошечные маленькие частные функции, которые используются только в другом приватном методе, интересующем мои классы. Я просто нахожу их уродливыми, поэтому я использовал лямбда в этом образце

Я не думаю, что будет влияние производительности на использование лямбда вместо функции

    
ответ дан Camilo Sanchez 20.08.2010 в 06:50
источник