Как вычислить ранг IEnumerable<T> и сохранить его в типе T

Я хочу вычислить ранг элемента в списке IEnumerable и назначить его члену. Но приведенный ниже код работает только при первом вызове. Второй вызов начинается с последнего значения ранга. Поэтому вместо выходных 012 и 012 я получаю 012 и 345

    class MyClass
    {
        public string Name { get; set; }
        public int Rank { get; set; }
    }

    public void SecondTimeRankEvaluvate()
    {
        MyClass[] myArray = new MyClass[] 
        {
            new MyClass() { Name = "Foo" },
            new MyClass() { Name = "Bar" },
            new MyClass() { Name = "Baz" }
        };

        int r = 0;
        var first = myArray.Select(s => { s.Rank = r++; return s; });

        foreach (var item in first)
        {
            Console.Write(item.Rank);
        }
        // Prints 012
        Console.WriteLine("");
        foreach (var item in first)
        {
            Console.Write(item.Rank);
        }
        // Prints 345
    }

Я понимаю что переменная r захватывается (закрытие) и используется повторно при вызове во второй раз. Я не хочу такого поведения. Есть ли какой-нибудь чистый способ вычислить ранг и присвоить его? Также r переменная (в реальном коде) не в той же области, где foreach петля присутствует. Это в функции, которая возвращает var first

3 ответа

Решение
var first = myArray.Select((s, i) => { s.Rank = i; return s; });

LINQ использует ленивую оценку и запускает Select расстаться каждый раз, когда вы используете myArray,

Вы можете принудительно выполнить оценку только один раз, сохранив результат в List,

+ Изменить

var first = myArray.Select(s => { s.Rank = r++; return s; });

в

var first = myArray.Select(s => { s.Rank = r++; return s; }).ToList();

Другим способом было бы присоединиться myArray с новой последовательностью, используя Zip каждый раз, как это

var first = myArray.Zip(Enumerable.Range(0, int.MaxValue), (s, r) =>
{
    s.Rank = r;
    return s;
});

Вы не должны использовать LINQ, если вы не запрашиваете коллекцию.

Чтобы обновить каждый элемент в массиве, используйте for цикл:

for (int i = 0; i < myArray.Length; i++)
{
    myArray[i].Rank = i;
}

Чтобы обновить каждый элемент в перечислимом, используйте foreach цикл:

int r = 0;
foreach (var item in myArray)
{
    item.Rank = r++;
}
Другие вопросы по тегам