Объем выражений Linq, определенных в цикле - PullRequest
2 голосов
/ 27 мая 2011

У меня вопрос по поводу выражений Linq, определенных в цикле.Следующая программа LinqPad C # демонстрирует поведение:

void Main()
{
    string[] data=new string[] {"A1", "B1", "A2", "B2" };
    string[] keys=new string[] {"A", "B" };

    List<Result> results=new List<Result>();

    foreach (string key in keys) {
        IEnumerable<string> myData=data.Where (x => x.StartsWith(key));     
        results.Add(new Result() { Key=key, Data=myData});          
    }   
    results.Dump();
}

// Define other methods and classes here
class Result {
    public string Key { get; set; }
    public IEnumerable<string> Data { get; set; }
}

По существу, «A» должно иметь данные [A1, A2], а «B» должно иметь данные [B1, B2].

Однако, когда вы запускаете это «A», получает данные [B1, B2], как и BIe, последнее выражение вычисляется для всех экземпляров Result.

Учитывая, что я объявил «myData» внутри цикла, почемуведя себя так, как будто я объявил это вне цикла?Например, он действует так, как я и ожидал, если бы я сделал это:

void Main()
{
    string[] data=new string[] {"A1", "B1", "A2", "B2" };
    string[] keys=new string[] {"A", "B" };

    List<Result> results=new List<Result>();

    IEnumerable<string> myData;                 
    foreach (string key in keys) {
        myData=data.Where (x => x.StartsWith(key));     
        results.Add(new Result() { Key=key, Data=myData});          
    }   
    results.Dump();
}

// Define other methods and classes here
class Result {
    public string Key { get; set; }
    public IEnumerable<string> Data { get; set; }
}

Я получаю желаемый результат, если форсирую оценку внутри итерации, это не мой вопрос.

Я спрашиваю, почему «myData», по-видимому, распределяется между итерациями, учитывая, что я объявил его в рамках одной итерации?

Кто-то вызывает Джона Скита ...; ^)

1 Ответ

5 голосов
/ 27 мая 2011

Это не myData, которым делятся - это key.И так как значения в myData оцениваются лениво, они зависят от текущего значения key.

Это ведет себя так, потому что область действия переменной итерации - весь циклне каждая итерация цикла.У вас есть переменная single key, значение которой изменяется, и это переменная , которая фиксируется лямбда-выражением.

Правильное исправление заключается только вскопируйте переменную итерации в переменную внутри цикла:

foreach (string key in keys) {
    String keyCopy = key;
    IEnumerable<string> myData = data.Where (x => x.StartsWith(keyCopy));     
    results.Add(new Result() { Key = key, Data = myData});
}

Подробнее об этой проблеме см. в блоге Эрика Липперта «Закрытие переменной цикла, считающейся вредной»: часть первая , часть вторая .

Это прискорбный артефакт способа, которым был разработан язык, но менять его сейчас было бы плохой идеей IMO.Хотя любой код, который изменил поведение, в основном был бы заранее взломан, это означало бы, что правильный код в (скажем) C # 6 будет действительным, но неправильным кодом в C # 5, и это опасная позиция, чтобы быть в.

...