Вызывать делегатов без параметров, но используя локальные параметры c # - PullRequest
2 голосов
/ 25 февраля 2010

Я часто выполняю следующие действия и не знаю, есть ли побочные эффекты или нет, но учту следующее в приложении WinForms C #. (прошу прощения за любые ошибки, так как я набираю код, а не копировать, вставляя что-либо)

int a = 1;
int b = 2;
int c = 3;
this.Invoke((MethodInvoker)delegate()
{
    int lol = a + b + c;
});

Что-то не так с этим? Или я должен сделать долгий путь> _ <</p>

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. Я думаю, что однажды я посмотрел на рефлектор, и он создал совершенно новый класс для хранения этих переменных.

Так это имеет значение? Могу ли я быть ленивым?

Редактировать: Обратите внимание, не важно, какое возвращаемое значение. В противном случае мне пришлось бы использовать свой собственный типизированный делегат, хотя я мог бы по-прежнему использовать локальные переменные, не передавая его!

Ответы [ 4 ]

3 голосов
/ 25 февраля 2010

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

// 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
1 голос
/ 25 февраля 2010

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

С другой стороны, ваш второй пример фактически передает данные через параметры. Это позволяет методу Invoke правильно распределять данные через границы потоков и избегать этих неприятных проблем с потоками. Если вы вызываете Invoke, скажем, из фонового работника, тогда вам следует использовать что-то вроде вашего второго примера (хотя я бы предпочел использовать делегаты Action и Func всякий раз, когда возможно, а не создавать новые).

1 голос
/ 25 февраля 2010

Нет ничего плохого в передаче локальных переменных, если вы понимаете, что получаете отложенное выполнение. Если вы напишите это:

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. Я полагаю, это будет аналогом того, что сказал Томас; если вы читаете локальные данные в делегате, он будет использовать любые значения, которые хранятся в переменных, когда действие фактически выполняется , а не когда объявлено . Это может дать некоторые интересные результаты, если переменные содержат ссылочные типы и вы вызываете делегат асинхронно.

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

0 голосов
/ 25 февраля 2010

С точки зрения стиля я бы выбрал вариант передачи paramater. Он выражает намерение намного легче передавать аргументы вместо любых окружающих типов (а также облегчает тестирование). Я имею в виду, вы могли бы сделать это:

public void Int32 Add()
{
    return this.Number1 + this.Number2
}

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

Я регулярно делаю это с парсами, такими как коллекции, которые все равно используются через ref и не требуют явного «возврата»:

public List<string> AddNames(List<String> names)
{
    names.Add("kevin");
    return names;
}

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

...