Закрытие и Лямбда в С#

Я получаю основные принципы замыканий и лямбда-выражений, но я пытаюсь обмануть то, что происходит за кулисами, и когда это/нецелесообразно использовать их в моем коде. Рассмотрим следующий пример, который берет набор имен и возвращает любые имена, начинающиеся с буквы C...

    static void Main(string[] args)
    {
        List<string> names = new List<string>();
        names.AddRange(new string[]
        {
            "Alan", "Bob", "Chris", "Dave", "Edgar", "Frank"
        });


        names.FindAll(x => x.StartsWith("C")).ForEach(
            i => Console.WriteLine(i));

    }

Во-первых, есть ли более прямой способ, которым я мог бы написать это выражение? Во-вторых, не "FindAll" собирается выделить память для новой коллекции, содержащей соответствующие элементы? Я определенно вижу, что синтаксис более изящный, но я хочу убедиться, что я не буду заниматься проблемами производительности позже по дороге при работе с большими коллекциями. Компилятор делает некоторую оптимизацию voodoo за кулисами, что делает мои проблемы недействительными?

Ответ 1

Да, FindAll создаст новый список. Вы хотите "Где", который вернет объект IEnumerable, который знает, как перебирать ваш существующий список:

foreach (string name in names.Where(n => n.StartsWith("C") ) ) 
{
    Console.WriteLine(name);
}

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

Ответ 2

Другие ответы, которые говорят, чтобы использовать "Где", являются правильными. Дополнительная точка: вы также можете использовать синтаксис понимания запроса, чтобы сделать "Where" более приятным:

   var query = from name in names where name.StartsWith("C") select name;
   foreach(var result in query) Console.WriteLine(result);

Обратите внимание, что в качестве стилистической проблемы я рекомендую, чтобы выражения не имели побочных эффектов, а утверждения всегда имели побочные эффекты. Поэтому я лично использовал бы выражение foreach, а не подвыражение ForEach для выполнения выходного побочного эффекта. Многие люди не согласны с этим, но я думаю, что это делает код более понятным.

Ответ 3

Вы должны использовать Where вместо FindAll. Where будет перебирать коллекцию для вашего условия и позволить вам выполнить ваше действие, а не создавать новую коллекцию, которая соответствует вашему условию, а затем повторять THAT и выполнять ваши действия.

Ответ 4

Вы правы, что использование метода List<T>.FindAll создаст и вернет новый List<T>.

Если вы можете использовать LINQ, тогда существует множество методов, которые передают свои результаты по одному элементу за раз, где это возможно, вместо того, чтобы возвращать полностью заполненную коллекцию:

foreach (var i in names.Where(x => x.StartsWith("C")))
{
    Console.WriteLine(i);
}

Нет встроенного метода ForEach, который действует на IEnumerable<T>, но тривиально писать собственное расширение, если вам действительно нужны эти функции:

names.Where(x => x.StartsWith("C")).ForEach(Console.WriteLine);

// ...

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
    foreach (T item in source)
    {
        action(item);
    }
}

Ответ 5

Что делает выражение специально закрытие лексическим охватом, no?

string prefix = "C";  
// value of prefix included in scope  
names.FindAll(x => x.StartsWith(prefix)).ForEach(...);    

или даже

Func filter = null;

{
    string prefix = "C";
    // value of prefix included in scope
    filter = x => x.StartsWith (prefix);    
}

// find all names starting with "C"
names.FindAll (filter).ForEach (...);       

Или я что-то упускаю или делаю необоснованные предположения?