uso sintaxis ordenar operadores introduccion from ejemplo comandos c# linq parameters ienumerable design-principles

c# - sintaxis - ¿Es seguro aceptar IEnumerable<T> como argumento después de la introducción de Linq?



sintaxis linq c# (3)

Hay algunas preguntas similares a esto que tratan con tipos de entrada y salida correctos como este . Mi pregunta es más acerca de la práctica correcta, el nombramiento de métodos, la elección del tipo de parámetro, la protección contra accidentes, etc. a raíz de Linq .

Linq casi en todas partes trata con IEnumerable y simplemente no es así, pero también nos presenta algo extraño llamado ejecución diferida. Ahora podríamos habernos equivocado al diseñar nuestros métodos (especialmente los métodos de extensión) cuando pensamos que la mejor idea es tomar el tipo más básico. Entonces nuestros métodos parecían:

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> lstObject) { foreach (T t in lstObject) //some fisher-yates may be }

El peligro obviamente es cuando mezclamos la función anterior con Linq perezoso y es tan susceptible.

var query = foos.Select(p => p).Where(p => p).OrderBy(p => p); //doesn''t execute //but var query = foos.Select(p => p).Where(p => p).Shuffle().OrderBy(p => p); //the second line executes up to a point.

Una gran edición aquí:

Para aclarar la línea anterior: mi pregunta no se trata de que la segunda expresión no se evalúe, en serio, no. Los programadores lo saben. Mi preocupación es acerca del método Shuffle que realmente ejecuta la consulta hasta ese punto. Vea la primera consulta, donde no se ejecuta nada. De manera similar, cuando construimos otra expresión Linq (que se debe ejecutar más adelante), nuestra función personalizada está reproduciendo el aguafiestas. En otras palabras, cómo hacer saber a la persona que llama Shuffle no es la función que querrían en ese punto de la expresión de Linq. Espero que el punto se lleve a casa. Disculpas! :) Aunque es tan simple como ir e inspeccionar el método, me pregunto cómo es que normalmente programan a la defensiva.

El ejemplo anterior puede no ser tan peligroso, pero entiendes el punto. Es decir, ciertas funciones (personalizadas) no van bien con la idea Linq de ejecución diferida. El problema no es solo sobre el rendimiento, sino también sobre los efectos secundarios inesperados.

Pero una función como esta funciona Linq con Linq :

public static IEnumerable<S> DistinctBy<S, T>(this IEnumerable<S> source, Func<S, T> keySelector) { HashSet<T> seenKeys = new HashSet<T>(); //credits Jon Skeet foreach (var element in source) if (seenKeys.Add(keySelector(element))) yield return element; }

Como puede ver, ambas funciones toman IEnumerable<> , pero la persona que llama no sabría cómo reaccionan las funciones. ¿Cuáles son las medidas generales de precaución que ustedes toman aquí?

  1. ¿Nombra nuestros métodos personalizados de forma adecuada para que le dé la idea a la persona que llama que es un buen augurio o no con Linq ?

  2. Mueva métodos perezosos a un espacio de nombres diferente, y mantenga Linq -ish en otro, para que al menos dé algún tipo de idea.

  3. ¿No se acepta un parámetro de IEnumerable para ejecutar métodos de forma immediately , sino que se toma un tipo más derivado o un tipo concreto en sí mismo, lo que deja a IEnumerable para los métodos perezosos? ¿Esto le impone la carga a la persona que llama para realizar la ejecución de posibles expresiones no ejecutadas? Esto es bastante posible para nosotros, ya que fuera del mundo de Linq apenas tratamos con IEnumerable s, y la mayoría de las clases de colección básicas implementan hasta ICollection por lo menos.

¿O algo más? Particularmente me gusta la tercera opción, y eso es con lo que estaba yendo, pero pensé en obtener sus ideas antes. He visto un montón de código (¡un poco de Linq como métodos de extensión!) Incluso de buenos programadores que aceptan IEnumerable y hacen una ToList() o algo similar dentro del método. No sé cómo soportan los efectos secundarios ...

Editar: Después de un voto a favor y una respuesta, me gustaría aclarar que no se trata de que los programadores no sepan cómo funciona Linq (nuestro dominio podría ser en algún nivel, pero eso es algo diferente), pero es que muchas funciones se escribieron sin tomar Linq en cuenta en ese momento. Ahora, encadenar un método de ejecución inmediata junto con los métodos de extensión de Linq lo vuelven peligroso. Entonces, mi pregunta es: ¿hay una guía general que los programadores sigan para que la persona que llama sepa qué usar del lado de Linq y qué no? Se trata más de programación defensiva que si-no-sabes-para-usar-eso-entonces-no-podemos-ayudar! (o al menos yo creo) ..


Como puede ver, ambas funciones toman IEnumerable<> , pero la persona que llama no sabría cómo reaccionan las funciones.

Eso es simplemente una cuestión de documentación. Mire la documentación de DistinctBy en MoreLINQ , que incluye:

Este operador usa la ejecución diferida y transmite los resultados, aunque se conserva un conjunto de claves ya vistas. Si se ve una clave varias veces, solo se devuelve el primer elemento con esa clave.

Sí, es importante saber qué hace un miembro antes de usarlo, y para las cosas que aceptan / devuelven cualquier tipo de colección, hay varias cosas importantes que debe saber:

  • ¿La colección será leída inmediatamente o diferida?
  • ¿Se transmitirá la colección mientras se devuelven los resultados?
  • Si el tipo de colección declarado aceptado es mutable, ¿intentará el método mutarlo?
  • Si el tipo de colección declarada devuelta es mutable, ¿será realmente una implementación mutable?
  • ¿Se cambiará la colección devuelta por otras acciones (por ejemplo, se trata de una vista de solo lectura en una colección que puede modificarse dentro de la clase)?
  • ¿Es null un valor de entrada aceptable?
  • ¿Es null un valor de elemento aceptable?
  • ¿Alguna vez el método devolverá null ?

Vale la pena considerar todas estas cosas, y la mayoría de ellas valía la pena considerarlas mucho antes de LINQ.

La moraleja es realmente: "Asegúrate de saber cómo se comporta algo antes de llamarlo". Eso fue cierto antes de LINQ, y LINQ no lo ha cambiado. Acaba de presentar dos posibilidades (ejecución diferida y resultados de transmisión) que rara vez estuvieron presentes antes.


La ejecución diferida no tiene nada que ver con los tipos. Cualquier método de linq que use iteradores tiene potencial para la ejecución diferida si usted escribe su código de esa manera. Select() , Where() , OrderByDescending() para, por ejemplo, todos los iteradores de uso y, por lo tanto, OrderByDescending() ejecución. Sí, esos métodos esperan un IEnumerable<T> , pero eso no significa que IEnumerable<T> sea ​​el problema.

Es decir, ciertas funciones (personalizadas) no van bien con la idea Linq de ejecución diferida. El problema no es solo sobre el rendimiento, sino también sobre los efectos secundarios inesperados.

¿Cuáles son las medidas generales de precaución que ustedes toman aquí?

Ninguna. Honestamente usamos IEnumerable todas partes y no tenemos el problema de que las personas no entienden los "efectos secundarios". "la idea Linq de la ejecución diferida" es fundamental para su utilidad en cosas como Linq-to-SQL. Me parece que el diseño de las funciones personalizadas no es tan claro como podría ser. Si las personas están escribiendo código para usar LINQ y no entienden lo que está haciendo, ese es el problema, no el hecho de que IEnumerable sea ​​un tipo base.

Todas sus ideas son solo envoltorios del hecho de que parece que usted tiene programadores que simplemente no entienden las consultas de linq. Si no necesita ejecución diferida, lo que parece que no es así, simplemente fuerce todo para evaluar antes de que salgan las funciones. Llame a ToList () en sus resultados y devuélvalos en una API consistente con la que el consumidor quiera trabajar: listas, matrices, colecciones o IEnumerables.


Usa IEnumerable donde sea que tenga sentido y codifica a la defensiva .

Como señaló SLaks en un comentario, la ejecución diferida ha sido posible con IEnumerable desde el principio, y desde que C # 2.0 introdujo la declaración de yield , ha sido muy fácil implementar la ejecución diferida usted mismo. Por ejemplo, este método devuelve un IEnumerable que utiliza la ejecución diferida para devolver algunos números aleatorios:

public static IEnumerable<int> RandomSequence(int length) { Random rng = new Random(); for (int i = 0; i < length; i++) { Console.WriteLine("deferred execution!"); yield return rng.Next(); } }

Entonces, cada vez que use foreach para recorrer un IEnumerable, debe suponer que podría pasar cualquier cosa entre iteraciones. Incluso podría arrojar una excepción, por lo que es posible que desee poner el bucle Foreach dentro de un try/finally .

Si la persona que llama pasa un IEnumerable que hace algo peligroso o nunca deja de devolver números (una secuencia infinita), no es tu culpa . No tiene que detectarlo y lanzar un error; solo agregue suficientes manejadores de excepciones para que su método pueda limpiarse por sí mismo en caso de que algo salga mal. En el caso de algo simple como Shuffle , no hay nada que hacer; solo deja que la persona que llama lidie con la excepción.

En el caso raro de que su método realmente no pueda manejar una secuencia infinita, considere aceptar un tipo diferente como IList . Pero incluso IList no lo protegerá de la ejecución diferida : usted no sabe qué clase está implementando IList o qué tipo de vudú está haciendo para encontrar cada elemento. En el caso súper raro de que realmente no pueda permitir que se ejecute ningún código inesperado mientras itera, debería aceptar una matriz , no cualquier tipo de interfaz.