2010-07-08 10 views
9

Yo solía pensar que List<T> is considered dangerous. Mi punto es que, creo que el valor predeterminado (T) no es un valor de retorno seguro. Many other people think so too considerar lo siguiente:
¿Es la Lista.Encontrar <T> considerada peligrosa? ¿Cuál es una mejor manera de hacer la lista <T>. Find (Predicado <T>)?

List<int> evens = new List<int> { 0, 2, 4, 6, , 8}; 
var evenGreaterThan10 = evens.Find(c=> c > 10); 
// evenGreaterThan10 = 0 #WTF 

predeterminado (T) para los tipos de valor es 0, por lo tanto, 0 es goona ser devuelto es el segmento de código anterior!
No me gustó esto, así que agregué un método de extensión llamado TryFind que devuelve un booleano y acepta un parámetro de salida además del predicado, algo similar al famoso método TryParse.
Editar:
Aquí es mi TryFind método de extensión:

public static bool TryFind<T>(this List<T> list, Predicate<T> predicate, out T output) 
{ 
    int index = list.FindIndex(predicate); 
    if (index != -1) 
    { 
    output = list[index]; 
    return true; 
    } 
    output = default(T); 
    return false; 
} 

¿Qué es un camino a la encuentren en las listas genéricas?

+0

Por qué no utilizar .Where (c => c> 10)? No soy un experto, pero debería funcionar ... –

+0

@Jouke - * Where() * devuelve una lista IEnumerable, no solo el primer elemento encontrado que * Find() * hace. – slugster

+0

Muchas gracias por señalar esto: en mi humilde opinión, es un defecto grave que hace que encuentre una solución que no se va (afortunadamente existe dónde y primero). Por otro lado Find() debería ser 100% equivalente a Where(). First(). – greenoldman

Respuesta

15

No lo hago. Hago .Where()

evens.Where(n => n > 10); // returns empty collection 

evens.Where(n => n > 10).First(); // throws exception 

evens.Where(n => n > 10).FirstOrDefault(); // returns 0 

El primer caso sólo devuelve una colección, por lo que puedo comprobar simplemente si el recuento es mayor que 0 para saber si hay coincidencias.

Al usar el segundo, envuelvo un bloque try/catch que maneja InvalidOperationException específicamente para manejar el caso de una colección vacía, y simplemente vuelve a lanzar (burbujear) todas las demás excepciones. Este es el que menos uso, simplemente porque no me gusta escribir declaraciones try/catch si puedo evitarlo.

En el tercer caso, estoy de acuerdo con que el código devuelva el valor predeterminado (0) cuando no existe ninguna coincidencia. Después de todo, dije explícitamente "obtener el primer valor o predeterminado". Por lo tanto, puedo leer el código y entender por qué sucede si alguna vez tengo un problema con él.

Actualización:

para .NET 2.0 usuarios, lo haría no recomendar el truco que se ha sugerido en los comentarios. Viola el acuerdo de licencia de .NET, y de ninguna manera será respaldado por nadie.

En cambio, veo dos maneras de ir:

  1. de actualización. La mayoría de las cosas que se ejecutan en 2.0 se ejecutarán (más o menos) sin cambios en 3.5. Y hay tanto en 3.5 (no solo LINQ) que es realmente que vale la pena el esfuerzo de actualizar para tenerlo disponible. Dado que hay una nueva versión de tiempo de ejecución de CLR para 4.0, hay más cambios de rotura entre 2.0 y 4.0 que entre 2.0 y 3.5, pero si es posible recomendaría actualizar todo el camino hasta 4.0. Realmente no hay una buena razón para estar sentado escribiendo código nuevo en una versión de un framework que ha tenido 3 lanzamientos principales (sí, cuento tanto 3.0, 3.5 y 4.0 como mayor ...) desde el que estás usando.

  2. Encuentra un problema al problema Find. Recomendaría usar solo FindIndex como lo hace, ya que el -1 que se devuelve cuando no se encuentra nada nunca es ambiguo, o implementar algo con FindIndex como lo hizo.No me gusta la sintaxis out, pero antes de escribir una implementación que no la use, necesito información sobre lo que desea que se devuelva cuando no se encuentre nada.
    Hasta entonces, el TryFind puede considerarse correcto, ya que se alinea con la funcionalidad anterior en .NET, por ejemplo Integer.TryParse. Y te dan una buena manera de manejar no se ha encontrado haciendo

    List<Something> stuff = GetListOfStuff(); 
    Something thing; 
    
    if (stuff.TryFind(t => t.IsCool, thing)) { 
        // do stuff that's good. thing is the stuff you're looking for. 
    } 
    else 
    { 
        // let the user know that the world sucks. 
    } 
    
+0

y ¿qué tiene que hacer la pobre alma ejecutiva .net 2.0? – Galilyou

+1

@Galilyou - ¿actualizar? ;) Puede usar LINQ en entornos .net 2.0, consulte http: // stackoverflow.com/questions/2138/linq-on-the-net-2-0-runtime –

+0

Soy un gran usuario de * FirstOrDefault *, pero al usarlo contra tipos de valor, terminas en la misma situación. Sin embargo, como dijiste, es posiblemente una mejor opción que usar * Find * debido a que es más explícito. Me pregunto cuántos errores sutiles se han introducido en los productos debido al comportamiento predeterminado (T) :) – slugster

2

Está bien si usted sabe que no hay default(T) valores en su lista o si el valor default(T) retorno no puede ser el resultado.

Puede implementar su propio fácilmente.

public static T Find<T>(this List<T> list, Predicate<T> match, out bool found) 
{ 
    found = false; 

    for (int i = 0; i < list.Count; i++) 
    { 
    if (match(list[i])) 
    { 
      found = true; 
     return list[i]; 
    } 
    } 
    return default(T); 
} 

y en el código:

bool found; 
a.Find(x => x > 5, out found); 

Otras opciones:

evens.First(predicate);//throws exception 
evens.FindAll(predicate);//returns a list of results => use .Count. 

Depende de qué versión del marco que puede utilizar.

1

Hacer una llamada a Exists() primero le ayudará con el problema:

int? zz = null; 
if (evens.Exists(c => c > 10)) 
    zz = evens.Find(c => c > 10); 

if (zz.HasValue) 
{ 
    // .... etc .... 
} 

un poco más largo aliento, pero cumple su cometido.

+0

¿Existe entonces Buscar? Eso es buscar dos veces, ¿verdad? – Galilyou

+0

@Galilyou: Sí. –

+0

Tuve que hacer esta opción, ninguna de las otras opciones apareció en C# 4.6/Visual Studio 2017 ... Sí, busca dos veces, pero solucionó mi problema hasta que encuentre un método mejor, ¡gracias! –

2

La misma respuesta que he dado en Reddit.

Enumerable.FirstOrDefault(predicate) 
+1

Es exactamente lo que el OP intenta evitar. –

1

Si hay un problema con el valor predeterminado de T, utilice anulable para obtener un valor por defecto más significativo:

List<int?> evens = new List<int?> { 0, 2, 4, 6, 8 }; 
var greaterThan10 = evens.Find(c => c > 10); 
if (greaterThan10 != null) 
{ 
    // ... 
} 

Esto también requiere ninguna llamada adicional de exists() en primer lugar.

0

Sólo por diversión

evens.Where(c => c > 10) 
    .Select(c => (int?)c) 
    .DefaultIfEmpty(null) 
    .First(); 
Cuestiones relacionadas