2009-08-20 23 views
11

Obtengo los principios básicos de cierres y expresiones lambda, pero estoy tratando de hacerme a la idea de lo que está sucediendo detrás de escena y cuándo es/no es práctico usarlos en mi código. Considere el ejemplo siguiente, que tiene una colección de nombres y devuelve los nombres que comienzan con la letra C ...Cierres y Lambda en 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)); 

    } 

En primer lugar, hay una manera más directa que podría haber escrito esta expresión? En segundo lugar, ¿no se va a asignar "FindAll" a la memoria de una nueva colección que contiene los elementos coincidentes? Definitivamente veo que la sintaxis es más elegante, pero quiero asegurarme de que no estoy entrando en problemas de rendimiento más adelante cuando trabaje con colecciones más grandes. ¿El compilador hace algo de vudú de optimización detrás de las escenas que hace que mis preocupaciones sean inválidas?

Respuesta

15

Sí, FindAll creará una nueva lista. ¿Quieres "Dónde", que devolverá un objeto IEnumerable que sabe cómo bucle sobre su lista existente:

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

Pero no hay cierre en ese código, porque no hay ninguna variable local a capturar.

+0

Ok, tal vez todavía no entiendo los fundamentos de los cierres. Sin embargo, todas las respuestas que obtuve aquí son excelentes y me muevo un poco más adelante ... gracias a todos. – lJohnson

+1

Eso me explica la diferencia entre los cierres y lambda: "no hay cierre en ese código, porque no hay una variable local para capturar". – TLDR

2

Debe usar Where en lugar de FindAll. Where iterará sobre la colección para su condición y le permitirá ejecutar su acción, en lugar de crear una nueva colección que cumpla con su condición, luego iterar sobre ESO y ejecutar su acción.

2

Tiene razón en que al usar el método List<T>.FindAll se creará y devolverá un nuevo List<T>.

Si usted es capaz de utilizar LINQ a continuación, hay muchos métodos que transmiten sus resultados un elemento a la vez, siempre que sea posible, en lugar de devolver una colección totalmente poblada:

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

No hay incorporada ForEach método que actúa sobre IEnumerable<T>, pero es trivial para escribir su propia extensión si realmente necesita esa funcionalidad:

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); 
    } 
} 
+1

Esto es exactamente cómo iba a hacerlo. También habría mencionado que la creación de lista se puede simplificar: 'List names = new List {" Alan "," Bob "," Chris "," Dave "," Edgar "," Frank "};' –

12

Las otras respuestas que dicen utilizar "Donde" son correctas. Un punto adicional: También puede utilizar la sintaxis de la comprensión de consulta para realizar el "dónde" ven mejor:

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

Tenga en cuenta que como una preocupación estilística, recomiendo que las expresiones no tienen efectos secundarios y las declaraciones siempre tienen efectos secundarios. Por lo tanto, yo personalmente usaría una declaración forecech en lugar de una subexpresión ForEach para realizar el efecto secundario de salida. Mucha gente no está de acuerdo con esto, pero creo que hace que el código sea más claro.

+3

Eso es una gran convención! –

0

Lo que hace que una expresión específicamente un cierre es el alcance léxico, ¿no?

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

o incluso

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 (...);  

O me estoy perdiendo algo o hacer suposiciones injustificadas?