2011-08-05 10 views
11

Últimamente, he estado escribiendo un montón de código que tiene este aspecto:¿La eliminación de elementos de una lista de C# <T> retiene los pedidos de otros artículos?

List<MyObject> myList = new List<MyObject>(); 
... 
for(int i = 0; i < myList.Count; ++i) 
{ 
    if(/*myList[i] meets removal criteria*/) 
    { 
    myList.RemoveAt(i); 
    --i; //Check this index again for the next item 
    //Do other stuff as well 
    } 
} 

y yo acabo de ser un poco paranoico que tal lista no retiene el orden de los objetos en la eliminación. No sé la especificación de C# lo suficientemente bien como para saberlo con certeza. ¿Alguien puede verificar que estoy o no estoy buscando problemas con este patrón?

EDITAR: Tal vez debería aclarar que lo anterior es un ejemplo muy simplificado y que suceden más cosas si el artículo necesita ser eliminado, así que no creo que List<T>.RemoveAll() sea terriblemente aplicable aquí. Aunque es una buena función. He agregado un comentario en el bloque anterior if() para mencionarlo específicamente.

+4

Sólo una nota al margen: debe recorrer la lista hacia atrás en lugar de hacia adelante. Digamos que eliminas la lista [1] (i = 1). Esto hará que la lista cambie, donde el elemento en la lista [2] ahora está en la lista [1]. Ahora cuando saltas a i = 2, te saltaste el elemento que ahora está en la lista [1]. –

+1

Debería poder simplificar enormemente este patrón con el método RemoveAll() –

+0

¿Por qué no usa linq para eso? – Andre

Respuesta

15

List<T>siempre mantener el orden relativo al agregar, insertar y quitar; no sería una lista si no fuera así.

Aquí está el (ILSpy'ed) código de RemoveAt():

public void RemoveAt(int index) 
{ 
    if (index >= this._size) 
    { 
     ThrowHelper.ThrowArgumentOutOfRangeException(); 
    } 
    this._size--; 
    if (index < this._size) 
    { 
     Array.Copy(this._items, index + 1, this._items, index, this._size - index); 
    } 
    this._items[this._size] = default(T); 
    this._version++; 
} 

Nota copiar la matriz index + 1-index; esos son los artículos que se cambian al por mayor y "exprimen" la matriz juntos. Pero definitivamente no hay reordenamiento de los elementos.

+0

Además de la conversación adicional (muy útil) sobre la ejecución de la lista en reversa y considerando 'RemoveAll()', esto responde la pregunta que realmente hice. ¡Gracias! – chaosTechnician

3

De Reflector:

public void RemoveAt(int index) 
{ 
    if (index >= this._size) 
    { 
     ThrowHelper.ThrowArgumentOutOfRangeException(); 
    } 
    this._size--; 
    if (index < this._size) 
    { 
     Array.Copy(this._items, index + 1, this._items, index, this._size - index); 
    } 
    this._items[this._size] = default(T); 
    this._version++; 
} 

Así, al menos, con la EM' aplicación - artículo en orden no cambia en RemoveAt.

2

Cuando llame al RemoveAt, se copiarán y desplazarán todos los elementos que siguen al índice que elimine.

Ordene la lista de posiciones en orden descendente y elimine los elementos en ese orden.

foreach (var position in positions.OrderByDescending(x=>x)) 
    list.RemoveAt(position); 

positions es la lista de índices. list es el que desea eliminar (contiene los datos reales).

10

Está en lo cierto, List<T>.RemoveAt no cambiará el orden de los elementos de la lista.

que el fragmento del embargo podría simplificarse a utilizar List<T>.RemoveAll así:

List<MyObject> myList = new List<MyObject>(); 
... 
myList.RemoveAll(/* Removal predicate */); 

Editar comentario siguiente:

myList.Where(/* Removal predicate */).ToList().ForEach(/* Removal code */); 
myList.RemoveAll(/* Removal predicate */); 
+2

Buen punto. No es necesario reinventar la rueda :) +1 –

+0

Esto también debería proteger contra el desplazamiento de la lista a medida que se elimina cada elemento (omitiendo el elemento directamente después del que se eliminó). Sugeriría moverse por la lista en orden inverso, ¡pero el suyo es mucho mejor! –

+0

He actualizado mi pregunta original para decir que dado que estoy haciendo algo más que simplemente eliminar el elemento, no creo que 'RemoveAll()' haga el truco para mí. – chaosTechnician

4

El orden debe mantenerse.Un mejor enfoque consiste en recorrer la lista en orden inverso:

for(int i = myList.Count - 1; i >= 0; i--) 
{ 
    if(/*myList[i] meets removal criteria*/) 
    { 
     myList.RemoveAt(i); 
    } 
} 

O usted podría utilizar el RemoveAll method:

myList.RemoveAll(item => [item meets removal criteria]); 
+0

¿Por qué es esto mejor? –

+4

@Kyle, el ciclo inverso para bucle es más fácil de seguir y no requiere que el índice disminuya una vez que se cumplen los criterios. Comenzando al final de la lista y trabajando hacia atrás, el bucle for continuará siendo preciso si se elimina un elemento. En cuanto a 'RemoveAll', es un proyecto de una sola línea con un predicado y no requiere que hagamos un bucle sobre la lista y administremos el índice. –

5

Aunque la respuesta aceptada es una gran respuesta a la pregunta original, la respuesta de la cigarra sugiere una alternativa enfoque.

Con CLR 4 (VS 2010) obtenemos otro enfoque, que tiene la ventaja adicional de ejecutar solo el predicado una vez por elemento (y hacer que sea conveniente evitar escribir el predicado dos veces en nuestro código).

Suponga que tiene un IEnumerable<string>:

IEnumerable<string> myList = new[] {"apples", "bananas", "pears", "tomatoes"}; 

Es necesario dividirla en dos listas en función de si los artículos pasan algunos criterios:

var divided = myList.ToLookup(i => i.Length > 6); 

El objeto devuelto es algo así como un Dictionary de liza. Supongamos que se desea mantener los que pasan los criterios:

myList = divided[true]; 

Y puede utilizar un bucle imperativo familiar para operar en los demás artículos:

foreach (var item in divided[false]) 
    Console.WriteLine("Removed " + item); 

Tenga en cuenta que no hay necesidad de usar List<T> específicamente. Nunca modificamos una lista existente, solo hacemos otras nuevas.

+0

+1 - ¡Esto está bien! No sabía que existía. –

Cuestiones relacionadas