C # сходит с ума, когда я объявляю переменные с тем же именем, что и в лямбда - PullRequest
2 голосов
/ 13 апреля 2010

У меня есть следующий код (генерирует квадратичную функцию с учетом a, b и c)
Func<double, double, double, Func<double, double>> funcGenerator = (a, b, c) => f => f * f * a + b * f + c;
До сих пор, прекрасно.
Но затем, если я пытаюсь объявить переменную с именем a, b, c или f, Visual Studio выдает "A local variable named 'f' could not be declared at this scope because it would give a different meaning to 'f' which is used in a child scope."
По сути, это терпит неудачу, и я понятия не имею, почему, потому что дочерняя область не имеет никакого смысла.

Func<double, double, double, Func<double, double>> funcGenerator =
    (a, b, c) => f => f * f * a + b * f + c;  
var f = 3; // Fails  
var d = 3; // Fine

Что здесь происходит?

Ответы [ 5 ]

12 голосов
/ 13 апреля 2010

Я думаю, что вы неправильно понимаете, что порядок объявлений не имеет значения для компилятора C # в отношении правил определения области действия.

Это:

Func<double, double, double, Func<double, double>> funcGenerator =
    (a, b, c) => f => f * f * a + b * f + c;  
var f = 3;
var d = 3;

Точно так же, как это:

var f = 3;
Func<double, double, double, Func<double, double>> funcGenerator =
    (a, b, c) => f => f * f * a + b * f + c;  
var d = 3;

Области не чувствительны к порядку. У вас есть локальная переменная с именем f, и вы пытаетесь объявить другую переменную с именем f внутри лямбды. Это незаконно в соответствии со спецификацией C #.

В частности, это будет противоречить способности лямбда-ов захватывать переменные. Например, этот код является допустимым:

int x = 3;
Func<int> func = () => x + 1;

Это полностью законно, и выполнение func() вернет 4. Вот почему вы не можете объявить другую переменную x здесь внутри лямбды - потому что лямбда действительно должна быть способна захватывать внешние x.

Просто измените имя одной из f переменных.

8 голосов
/ 13 апреля 2010

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

7 голосов
/ 13 апреля 2010

Может быть довольно сложно выяснить, какое именно правило C # вы нарушили. Как государственная служба, я написал это удобное руководство, которое объясняет различия между некоторыми из более легко путаемых правил определения области действия:

http://ericlippert.com/tag/simple-names/

(Начните снизу; в обратном хронологическом порядке.)

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

namespace A 
{
  public class B 
  {
      private int c;
      protected int d;
      public void E(int f)
      {
         int g = f;
         Func<int, int> h = i => g * i;
      }
  }
  public class K : B { }
}

Сфера действия А везде. Области действия B и K всюду находятся внутри объявления A. Область действия c находится везде внутри B. Области действия d и E - это содержимое B, K и любого класса, производного от B или K. Области действия f и g и h - это тело E. Объем i - это тело лямбды.

Обратите внимание, что существует разница между областью действия и областью доступности. B, K и E доступны везде, но имеют только в области в определенных местах.

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

2 голосов
/ 13 апреля 2010

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

int x;
Action a = () => { x = 3; };

Ничего страшного, он присваивает 3 внешним x.

int x;
Action<int> a = x => { x = 3; };

Это не хорошо - на какой x мы назначаем 3?

В вашем примере единственная разница - это порядок объявления. Это приведет к ошибке

Action a = () => { x = 3; };
int x = 2;

Компилятор скажет, что вы не можете ссылаться на x до его объявления. Если вы затем заставите лямбду принимать параметр с тем же именем, мы получим примерно ваш пример:

Action<int> a = x => { x = 3; };
int x;

Если бы это сработало, компилятор в основном следовал бы правилу вида: «попробуйте различные возможные интерпретации кода, и в зависимости от того, какой из них не ошибочен, предположите, что это подразумеваемое значение». C # использует немного более безопасный подход и ожидает, что вы будете конкретны и недвусмысленны, а не будете полагаться на такие правила, чтобы «выбрать победителя».

0 голосов
/ 13 апреля 2010

Переменная объявляется внутри области функции. Поскольку лямбда-код компилируется / переписывается в некотором коде внутри той же функции, это имеет некоторый смысл. Я думаю, что цикл for, где вы определяете переменную как «первый аргумент» цикла for, является единственным исключением.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...