Я нахожу, что делаю следующее много, и я не знаю, есть ли какие-либо побочные эффекты или нет, но рассмотрим следующее в приложении WinForms С#. (пожалуйста, извините любые ошибки, поскольку я набираю код, а не копирую вставку)
int a = 1;
int b = 2;
int c = 3;
this.Invoke((MethodInvoker)delegate()
{
int lol = a + b + c;
});
С этим что-то не так? Или я должен делать длинный путь > _ <
int a = 1;
int b = 2;
int c = 3;
TrippleIntDelegate ffs = new TrippleIntDelegate(delegate(int a_, int b_, int c_)
{
int lol = a_ + b_ + c_;
});
this.Invoke(ffs);
Разница заключается в том, что параметры передаются вместо использования локальных переменных, некоторые довольно милые магии .net. Я думаю, что однажды посмотрел на него отражатель и создал совершенно новый класс для хранения этих переменных.
Как это важно? Могу ли я быть ленивым?
Изменить: обратите внимание, не заботятся о возвращаемом значении. В противном случае мне пришлось бы использовать свой собственный типизированный делегат, хотя я все равно мог использовать локальные переменные, не передавая их!
Нет ничего плохого в передаче локальных переменных, если вы понимаете, что получаете отсроченное исполнение. Если вы пишете это:
int a = 1;
int b = 2;
int c = 3;
Action action = () => Console.WriteLine(a + b + c);
c = 10;
action(); // Or Invoke(action), etc.
Результат этого будет 13, а не 6. Я полагаю, это было бы аналогией того, что сказал Томас; если вы читаете locals в делегате, он будет использовать любые значения, которые сохраняются переменными, когда действие действительно выполняется, а не когда оно объявлено. Это может привести к некоторым интересным результатам, если переменные содержат ссылочные типы, и вы вызываете делегат асинхронно.
Кроме этого, есть много веских причин для передачи локальных переменных в делегат; среди прочего, его можно использовать для упрощения кода потоков. Это прекрасно делать, пока вы не становитесь неаккуратным.
Как вы его используете, на самом деле это не имеет никакого значения. Однако в первом случае ваш анонимный метод захватывает переменные, которые могут иметь довольно большие побочные эффекты, если вы не знаете, что делаете. Например:
// No capture :
int a = 1;
Action<int> action = delegate(int a)
{
a = 42; // method parameter a
});
action(a);
Console.WriteLine(a); // 1
// Capture of local variable a :
int a = 1;
Action action = delegate()
{
a = 42; // captured local variable a
};
action();
Console.WriteLine(a); // 42
Ну, все остальные ответы, похоже, игнорируют многопотоковый контекст и проблемы, возникающие в этом случае. Если вы действительно используете это из WinForms, ваш первый пример может генерировать исключения. В зависимости от фактических данных, которые вы пытаетесь ссылаться от вашего делегата, поток, на который фактически вызывается код, может иметь или не иметь права доступа к данным, которые вы закрываете.
С другой стороны, ваш второй пример фактически передает данные через параметры. Это позволяет методу Invoke правильно маршалировать данные по границам потоков и избегать этих неприятных проблем с потоками. Если вы вызываете Invoke из, скажем, фонового работника, тогда вы должны использовать что-то вроде своего второго примера (хотя я бы предпочел использовать Action < T,... > и Func < T,... > делегаты, когда это возможно, а не создавать новые).
ThreadPool
, передача каждой из них ManualResetEvent
для установки по завершении, а затем ожидание на всех дескрипторах ожидания. Гораздо проще, чем альтернативы (в любом случае .NET 3.5). Нет проблем с многопоточностью, подобной этой, хотя, очевидно, это становится проблемой, если вы раскручиваете поток и затем позволяете локальным пользователям выйти из области видимости до завершения потока (как я уже отмечал ранее).
С точки зрения стиля я бы выбрал вариант передачи paramater. Он выражает намерение намного проще передать args instad для принятия каких-либо амбиций (а также облегчает тестирование). Я имею в виду, вы могли бы сделать это:
public void Int32 Add()
{
return this.Number1 + this.Number2
}
но он не является ни проверяемым, ни ясным. Сиг, принимающий параметры, намного более ясен для других, что делает этот метод... он добавляет два числа: не произвольный набор чисел или что-то еще.
Я регулярно делаю это с помощью парм, подобных коллекциям, которые все равно используются с помощью ref, и не нужно явно "возвращать":
public List<string> AddNames(List<String> names)
{
names.Add("kevin");
return names;
}
Несмотря на то, что коллекция имен передается по ref и, следовательно, не требуется явно возвращать, мне гораздо яснее, что метод принимает список и добавляет к нему, а затем возвращает его обратно. В этом случае существует техническая причина нет, чтобы написать сиг, таким образом, но для меня есть веские причины в отношении ясности и, следовательно, поддержания соответствия.