2009-03-17 17 views
6

Linq es una adición impresionante a .NET y he encontrado que me ha servido bien en muchas situaciones, aunque solo estoy empezando a aprender sobre cómo usar Linq.Linq advertencias

Sin embargo, en la lectura que he estado haciendo sobre Linq, he descubierto que hay algunas cosas sutiles que un desarrollador necesita para estar al tanto y que pueden ocasionar problemas.

He incluido una advertencia definitiva que he encontrado que es el resultado de la ejecución diferida.

Así que me pregunto, ¿qué otras advertencias existen para Linq que los desarrolladores nuevos en Linq deberían saber?

Respuesta

5

La creación de una consulta dentro de un bucle foreach

IEnumerable<char> query = "Not what you might expect"; 
foreach(char vowel in "aeiou") 
{ 
    query = query.Where(c => c != vowel); 
} 

El código anterior sólo elimina la "u" de la cadena debido a la ejecución diferida.

Con el fin de eliminar todos los vocales que tiene que hacer lo siguiente:

IEnumerable<char> query = "Not what you might expect"; 
foreach(char vowel in "aeiou") 
{ 
    char temp = vowel; 
    query = query.Where(c => c != temp); 
} 
+1

hmmm ... esta pregunta no era tan popular como lo había esperado que sería. Supongo que como mi respuesta obtuvo la mayor cantidad de votos, tendré que aceptarla como respuesta ... si eso cambiara, consideraría aceptar una respuesta diferente. – mezoid

+0

esto no es una advertencia de Linq, es solo general * * lambda ** advertencia, una especie de implementación interrumpida específica solo para la era pre-C# 5. En C# 5 esto funciona como se esperaba. – nawfal

3

Creo que LINQ es bastante sólida, y no hay un montón de grandes advertencias. Casi todos los "problemas" con los que me he encontrado son el resultado de la ejecución diferida, y no es realmente un problema, sino una forma diferente de pensar.

El mayor problema al que me he enfrentado: LINQ es un elemento que cambia las reglas del juego (o, al menos, una regla dobladora) cuando se trata de perfilar para el rendimiento. La ejecución diferida puede hacer que sea mucho más difícil perfilar una aplicación a veces, y también puede cambiar drásticamente las características de rendimiento en tiempo de ejecución de formas inesperadas. Ciertas operaciones LINQ parecen casi mágicas con lo rápido que son, y otras tardan mucho más de lo que esperaba, pero no siempre es obvio por el código o los resultados del generador de perfiles.

Dicho esto, en general, la ejecución diferida compensa con creces los casos en los que se ralentiza las rutinas codificadas a mano. Prefiero el código más simple y más limpio para el código que reemplazó.

Además, he descubierto que cuanto más utilizo LINQ to Objects, más tengo que volver a pensar en mi diseño y volver a trabajar en mis colecciones en general.

Por ejemplo, nunca me había dado cuenta de la frecuencia con que estaba exponiendo IList en lugar de IEnumerable cuando no era absolutamente necesario hasta que comencé a usar linq en objetos con frecuencia. Ahora entiendo completamente por qué las pautas de diseño de MS advierten contra el uso de IList con demasiada frecuencia (por ejemplo, no devuelva IList solo para la propiedad Count, etc.). Cuando tenía métodos que tomaban IList, pasar los resultados de IEnumerable de una consulta de linq requiere .ToList() o una reelaboración de la API del método.

Pero casi siempre vale la pena reconsiderarlo: he encontrado muchos lugares en los que pasar un enumerable y utilizar LINQ resultó en una gran mejora. ganancias. La ejecución diferida es maravillosa si lo piensas y aprovechas al máximo. Por ejemplo, usar .Take() para restringir una colección a los primeros 2 elementos, si eso es todo lo que se necesita, es un pre-linq un poco más desafiante, y ha acelerado dramáticamente algunos de mis loops más desagradables.

1

Buena pregunta. Como señala Reed, todos provienen principalmente de deferred execution (pero a diferencia de él, me parece un inconveniente. Solo estoy pensando por qué no se pueden llevar a cabo ejecuciones diferidas memorizando el estado). Aquí hay un par de ejemplos: todos son más o menos variantes del problema de ejecución diferida.

1) Soy demasiado vago para hacer algo a tiempo

LINQ se ejecuta sólo en la demanda.

Un error común que los novatos (yo mismo en el pasado incluido) hacen es no saber sobre la ejecución diferida. Por ejemplo, algo así como

var p = listOfAMillionComplexItems.OrderBy(x => x.ComplexProperty); 

carreras en un santiamén, pero la clasificación actual no se completa hasta que enumerar la lista, es decir, la ejecución no se completa hasta que necesite el resultado de la ejecución. Para ejecutarlo, necesita algo como:

foreach(var item in p)... 
//or 
p.Count(); 
//or 
p.ToList(); 
//etc 

Véalos como consultas SQL. Si tiene

var query = from i in otherValues where i > 5 select i; 

creo que es similar a la escritura

string query = "SELECT i FROM otherValues WHERE i > 5"; 

¿La última ejecución de una llamada a DB? No. Tiene que

Execute(query); 

Es lo mismo aquí con Linq.

2) que viven en el presente

Sea cauteloso sobre variables dentro de las expresiones LINQ cambiándose más adelante.

Para estar seguro, haga una copia de seguridad de las variables primero y luego use la copia de seguridad en la consulta si la variable puede cambiar posteriormente antes de la ejecución real de la consulta.

From here:

decimal minimumBalance = 500; 
var customersOver500 = from c in customers 
         where c.Balance > minimumBalance 
         select c; 

minimumBalance = 200; 
var customersOver200 = from c in customers 
         where c.Balance > minimumBalance 
         select c; 

int count1 = customersOver500.Count(); 
int count2 = customersOver200.Count(); 

Supongamos que tenemos cuatro clientes con los siguientes saldos: 100, 300, 400 y 600. ¿Qué va a ser COUNT1 y Cont2? Ambos serán 3. El "customersOver500" hace referencia a la variable "minimumBalance", pero el valor no se obtiene hasta que los resultados de la consulta se repiten (a través de un ciclo for/each, una llamada ToList() o incluso un "Count() "llamar como se muestra arriba). En el momento en que se utiliza el valor para procesar la consulta, el valor de minimumBalance ya ha cambiado a 200, por lo que ambas consultas LINQ producen resultados idénticos (clientes con un saldo superior a 200).

3) Mi memoria es demasiado débil como para recordar los objetos de valor del pasado

Lo mismo que el anterior, el contexto de ser un poco diferente.

o esto desde el mismo sitio:

consideran este sencillo ejemplo de un método que utiliza LINQ a SQL para obtener una lista de los clientes:

public IEnumerable<Customer> GetCustomers() 
{ 
    using(var context = new DBContext()) 
    { 
     return from c in context.Customers 
       where c.Balance > 2000 
       select c; 
    } 
} 

parece bastante inofensiva - hasta que obtenga una "ObjectDisposedException" cuando intente enumerar la colección. ¿Por qué? Porque LINQ en realidad no realiza la consulta hasta que intente y enumere los resultados. La clase DBContext (que expone la colección Clientes) se descarta cuando finaliza esta llamada. Una vez que intenta y enumera a través de la colección, se hace referencia a la clase DBContext.Customers y obtiene la excepción.

4) No trate de cogerme, todavía podría escabullo

try-catch no tiene sentido para una declaración si no se usa con prudencia.

En su lugar, el manejo de excepciones globales será mejor aquí.

try 
{ 
    wallet = bank.Select(c => Convert.ToInt32("")); 
} 
catch (Exception ex) 
{ 
    MessageBox.Show("Cannot convert bad int"); 
    return; 
} 

foreach(int i in wallet) 
    //kaboom! 

Ni que sale el mensaje de error correcto ni la función es dejar de fumar por return.

5) No sólo estoy unpunctual, pero no aprenden de los errores, así

LINQ se ejecuta cada vez que enumerar sobre ellos. Así que no reutilices los enumerables de Linq.

Suponga que tiene un IQueryable o IEnumerable de regresar de una expresión LINQ. Ahora, al enumerar la colección, ¿se ejecutará la instrucción, pero solo una vez? No, cada vez que lo haces Esto me había mordido en el pasado. Si usted tiene:

var p = listOfAMillionComplexItems.OrderBy(x => x.ComplexProperty); 
MessageBox.Show(p.Count().ToString()); //long process. 
MessageBox.Show(p.Count().ToString()); //long process still. 

Así que es mejor hacer

int i = p.Count(); //store in a variable to access count 
//or better 
var list = p.ToList(); //and start using list 

6) Si usted no sabe usarme, puedo causar efectos secundarios!

Lo mismo que el anterior, solo para mostrar cómo la reutilización de enumerables de Linq puede causar un comportamiento no deseado.

vigile que no se hace la programación de efectos secundarios (ya que re-enumeración de LINQ es mucho más común) para dar un ejemplo salvaje,

p = bag.Select((t, i) => {if(i == 1) MessageBox.Show("Started off"); return t;}); 

Si enumerar el doble que saber qué cosa no deseada puede pasar.

7) Tenga cuidado con el fin estoy ejecuta cuando el encadenamiento

No sólo para las variables, incluso las funciones de Linq encadenados se pueden ejecutar con el fin diferente de lo que se esperaría normalmente (aunque el comportamiento es correcto) . No piense que es imperativo (paso a paso), piense cómo Linq puede posiblemente ejecutarlo.

Por ejemplo,

var d = Enumerable.Range(1, 100); 
var f = d.Select(t => new Person()); 
f = f.Concat(f); 
f.Distinct().Count() ?? 

¿Cuál será el número de los distintos en f? Supongo que 100, no, pero es 200. El problema es que cuando se lleva a cabo la ejecución real de la lógica de concatenación, f sigue siendo d.Select(t => new Person()no ejecutado. Por lo tanto, esto cede efectivamente en

f = d.Select(t => new Person()).Concat(d.Select(t => new Person())); 

que luego tiene 200 miembros distintos. Here's a link for the actual problem

8) Oye, en realidad somos más inteligentes de lo que piensas.

No es una advertencia en sí, pero hay muchos casos en que Linq puede superar su programa de estilo imperativo. Entonces, antes de optimizar, piense un poco, e incluso comparta.

El motivo por el que la ejecución diferida se ejecuta básicamente a pedido hace que Linq sea mucho más eficiente de lo que parece. El bloque iterador "cede" un elemento a la vez, según lo exigido, lo que le permite detener la ejecución cuando ya no es necesario. Aquí es una muy buena pregunta que detalla exactamente eso: Order of LINQ extension methods does not affect performance?

9) yo no estoy destinado a crujir número

Abuso de LINQ puede hacer que el código ineficiente, así como menos legible.

Para los algoritmos de cálculo numérico, Linq no es la herramienta adecuada, especialmente para grandes conjuntos de datos cuya complejidad puede escalar exponencialmente. A veces, solo dos bucles for serían suficientes. Lo mismo puede aplicarse al SQL sin formato cuando se compara con LINQ a SQL.

10) me contrata para el trabajo correcto

Preguntar LINQ a la mente de su oficina normal es mala opción de programación, algo que va en contra de la legibilidad.

Algunos por ejemplo:

medicines.Any(p => 
{ 
    Console.WriteLine(p); 
    return false; 
}); 

para un foreach en una enumerable.

o

medicines = medicines.Select(p => 
{ 
    p.Id = 3; 
    return p; 
}); 

sólo mala herramientas.

11) Depuración y perfiles pueden ser una pesadilla

Es difícil seguir lo que está sucediendo bajo el capó una expresión LINQ de VS

No

que su todo imposible, pero su poco de una tarea para depurar una consulta linq tan eficientemente como un código no lineal del propio VS. La creación de perfiles también se vuelve un poco más difícil debido a la naturaleza de la ejecución diferida. ¡Pero no debería impedir que nadie haga trivialmente uno o dos forros!


¡Un montón de advertencias relacionadas con la ejecución diferida más o menos! A ditto question here. Algunos leer relacionados en SO:

Examples on when not to use LINQ

Pros and Cons of LINQ (Language-Integrated Query)

What is the biggest mistake people make when starting to use LINQ?

drawbacks of linq