c# - La mejor manera de deshacerse de una lista
asp.net garbage-collection (14)
Estoy teniendo un objeto List. ¿Cómo puedo deshacerme de la lista?
Por ejemplo,
List<User> usersCollection =new List<User>();
User user1 = new User();
User user2 = new User()
userCollection.Add(user1);
userCollection.Add(user2);
Si configuro userCollection = null;
¿lo que sucederá?
foreach(User user in userCollection)
{
user = null;
}
¿Cuál es el mejor?
¿Por qué quieres disponer de la lista? El GC lo hará por usted si ya no hay referencias.
Recolección de basura: msdn.microsoft.com/en-us/library/0xy59wtx.aspx
En primer lugar, no puede "deshacerse" de una lista ya que no es IDisposable
, y no puede forzarla a que se recopile porque no es así como funciona C # . Por lo general , no harías nada aquí. Entonces, ¿cuándo podríamos necesitar hacer algo ?
- Si se trata de una variable de método, y su método va a salir en un momento, no haga nada: deje que el GC se preocupe por él en algún momento después de que el método haya existido.
- Si se trata de un campo (variable de instancia) y el objeto va a quedar fuera de alcance en un momento, no haga nada: deje que el GC se preocupe por él en algún momento después de que la instancia sea inalcanzable.
La única vez que necesita algo es si se trata de un campo (o variable de bloque / variable de bloque capturado / etc) y la instancia (/ delegado / iterador) va a vivir mucho más tiempo - entonces quizás establezca el campo de lista en nulo . Sin embargo, tenga en cuenta que si cualquier otro código todavía tiene una referencia a la lista, todo seguirá siendo alcanzable.
La mejor manera es
userCollection= null;
Than GC se encargará del descanso.
Otra idea es usar corchetes que incluyan el alcance de su variable que desea conservar.
por ejemplo.
void Function()
{
... some code here ....
{ // inside this bracket the usersCollection is alive
// at the end of the bracet the garbage collector can take care of it
List<User> usersCollection =new List<User>();
User user1 = new User();
User user2 = new User()
userCollection.Add(user1);
userCollection.Add(user2);
foreach(User user in userCollection)
{
}
}
... other code here ....
}
No has proporcionado suficiente contexto. El alcance es crítico aquí.
Creo que el GC debe ser lo suficientemente inteligente como para tratar con la memoria asignada a los usuarios y la colección sin tener que establecer nada a nulo.
Si la colección elimina a los usuarios que no son necesarios de la colección, y ningún otro objeto se refiere a ellos, se someterán a GC sin que tengas que dar ninguna pista.
El GC no limpiará un objeto mientras haya una referencia en vivo. Elimina todas las referencias y puede hacer su trabajo.
Otra idea para esta publicación ... Si desea asegurarse de que todos los miembros de una colección se eliminen correctamente, puede usar el siguiente método de extensión:
public static void DisposeAll(this IEnumerable set) {
foreach (Object obj in set) {
IDisposable disp = obj as IDisposable;
if (disp != null) { disp.Dispose(); }
}
}
Esto se ve a través de la colección para cualquier miembro que implementa IDisposable
y deshacerse de ella. Desde su código de ejecución, puede limpiar la lista de esta manera:
usersCollection.DisposeAll();
usersCollection.Clear();
Esto asegurará que todos los miembros tengan la oportunidad de liberar recursos y la lista resultante esté vacía.
He encontrado escenarios en los que, cuando se procesan grandes cantidades de datos, el GC no se limpia hasta que la colección ha salido del alcance (técnicamente, el GC hace su recolección cuando lo considera oportuno y esto puede no serlo). cuando la colección queda fuera del alcance).
En estos escenarios (raros), he usado la siguiente clase:
public class DisposableList<T> : List<T>, IDisposable
{
public void Dispose()
{
}
}
Puede usarlo como una lista normal, por ejemplo
var myList = new DisposableList<MyObject>();
Luego llame al método Dispose cuando haya terminado:
myList.Dispose();
O, alternativamente, declararlo en una declaración de uso:
using (var myList = new DisposableList<MyObject>())
{
...
}
Esto hace que el GC haga su recolección inmediatamente una vez que la Lista Desechable esté fuera del alcance o eliminada.
Otro ejemplo más de un método de extensión que puede usar para eliminar una lista de objetos que implementan la interfaz IDisposable. Este usa la sintaxis LINQ.
public static void Dispose(this IEnumerable collection)
{
foreach (var obj in collection.OfType<IDisposable>())
{
obj.Dispose();
}
}
Y una implementación genérica que se trabajará (aparecerá en la List<T>
métodos List<T>
) si el elemento implementado IDisposable
public static class LinqExtensions
{
public static void DisposeItems<T>(this IEnumerable<T> source) where T : IDisposable
{
foreach(var item in source)
{
item.Dispose();
}
}
}
Para ser utilizado de esta manera
if(list != null)
{
list.DisposeItems();
list.Clear();
}
Hay una manera mucho mejor cuando se usa System.Reactive.Disposeables :
Simplemente inicialice una nueva propiedad de tipo CompositeDisposable
y agregue los desechables a esta colección. Entonces deseche solo este.
Aquí hay un ejemplo de código de cómo hacerlo en un WPF / UWP ViewModel típico sin indroducir ninguna pérdida de memoria:
public sealed MyViewModel : IDisposable
{
// ie. using Serilog
private ILogger Log => Log.ForContext<MyViewModel>();
// ie. using ReactiveProperty
public ReactiveProperty<string> MyValue1 { get; }
= new ReactiveProperty<string>(string.Empty);
public ReactiveProperty<string> MyValue1 { get; }
= new ReactiveProperty<string>(string.Empty);
// this is basically an ICollection<IDisposable>
private CompositeDisposable Subscriptions { get; }
= new CompositeDisposable();
public MyViewModel()
{
var subscriptions = SubscribeToValues(); // Query
Subscriptions.AddRange(subscriptions); // Command
}
private IEnumerable<IDisposable> SubscribeToValues()
{
yield return MyValue1.Subscribe(
value => DoSomething1(value),
ex => Log.Error(ex, ex.Message),
() => OnCompleted());
yield return MyValue2.Subscribe(
value => DoSomething2(value),
ex => Log.Error(ex, ex.Message),
() => OnCompleted());
}
private void DoSomething1(string value){ /* ... */ }
private void DoSomething2(string value){ /* ... */ }
private void OnCompleted() { /* ... */ }
implementar IDisposable
esta manera:
#region IDisposable
private ~MyViewModel()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
private bool _isDisposed;
private Dispose(bool disposing)
{
if(_isDisposed) return; // prevent double disposing
// dispose values first, such that they call
// the onCompleted() delegate
MyValue1.Dispose();
MyValue2.Dispose();
// dispose all subscriptions at once
Subscriptions.Dispose();
// do not suppress finalizer when called from finalizer
if(disposing)
{
// do not call finalizer when already disposed
GC.SuppressFinalize(this);
}
_isDisposed = true;
}
#endregion
}
y aquí está la clase de extensión para obtener el método .AddRange()
:
public static class CollectionExtensions
{
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> values)
{
foreach(var value in values)
{
collection.Add(value);
}
}
}
Ver también
-
BooleanDisposable
permite consultar si el objeto ya se eliminó -
CancellationDisposable
es como BooleanDisposable pero con una ficha de cancelación -
ContextDisposable
te permite tirar en un contexto de hilo dado -
MultipleAssignmentDisposable
reemplaza un desechable por otro sin desechar el viejo desechable -
SerialDisposable
reemplaza el viejoSerialDisposable
con otro mientras desecha el viejo desechable -
SingleAssignmentDisposable
almacena un desechable que no se puede replantear con otro desechable
Veo muchas respuestas llamando Deshacer un objeto dentro de un ciclo foreach sobre una colección. Dado que Dispose solo marca el objeto que se eliminará la próxima vez que se ejecute el recolector de elementos no utilizados, funcionará correctamente. En teoría, sin embargo, deshacerse de un artículo podría modificar la colección y romper el foreach, por lo que sería más robusto recoger primero esos objetos desechables, limpiar la lista original y llamar al disposer dentro de un bucle for o while comenzando desde el final y eliminando el objeto en cada iteración, por ejemplo llamando al siguiente método:
public static void DisposeItemsInList<T>(this IList<T> list) where T : IDisposable
{
DeleteItemsInList(list, item => item.Dispose());
}
public static void DeleteItemsInList<T>(this ICollection<T> list, Action<T> delete)
{
if (list is IList && !((IList)list).IsFixedSize)
{
while (list.Count > 0)
{
T last = list.Last();
list.Remove(last);
delete?.Invoke(last);
}
}
else
{
for (int i = 0; i < list.Count; i++)
{
delete?.Invoke(list.ElementAt(i));
}
}
}
De hecho, estoy usando DeleteItemsInList para otros fines, por ejemplo, para eliminar archivos: DeleteItemsInList (File.Delete))
Como han indicado, en general, no debería ser necesario disponer de tal lista. Un caso en el que elimino el elemento de una lista está trabajando con Stream, recojo algunos datos, transformo los datos de ellos y luego elimino estos flujos y solo guardo mis objetos transformados para su posterior procesamiento.
Como todo el mundo ha mencionado, deje GC, es la mejor opción y no fuerce el GC. Establecer la variable a nulo marcará la variable para el GC.
si después de obtener más información: Mejor práctica para forzar la recolección de basura en C #
La mejor idea es dejarlo al recolector de basura. Su foreach
no hará nada, ya que solo la referencia se establecerá en null
no en el elemento de la lista. Establecer la lista como null
podría ocasionar que la recolección de basura ocurriera más tarde de lo que podría haber ocurrido (ver esta publicación C #: ¿las variables de objeto deberían asignarse a nulo? ).
No estoy de acuerdo en que no deberías hacer nada si ya no necesitas los objetos en la lista. Si los objetos implementan la interfaz System.IDispoable, el diseñador del objeto pensó que el objeto contiene recursos escasos. Si no lo necesita y simplemente asigna nulo al objeto, estos recursos escasos no se liberan hasta que el recolector de basura finaliza el objeto. Mientras tanto, no puedes usar este recurso para otra cosa.
Ejemplo: Considere que crea un mapa de bits a partir de un archivo y decide que ya no necesita el mapa de bits ni el archivo. El código podría verse así:
using System.Drawing;
Bitmap bmp = new Bitmap(fileName);
// do something with bmp until not needed anymore
bmp = null;
File.Delete(fileName); // ERROR, filename is still accessed by bmp.
El buen método sería:
bmp.Dispose();
bmp = null;
File.Delete(fileName);
Las mismas cuentas para objetos en una lista, o cualquier colección. Todos los objetos en la colección que son IDisposables deben ser desechados. El código debería ser como:
private void EmptySequence (IEnumerable sequence)
{ // throws away all elements in the sequence, if needed disposes them
foreach (object o in sequence)
{
System.IDisposable disposableObject = o as System.IDisposable;
o = null;
if (disposableObject != null)
{
disposableObject.Dispose();
}
}
}
O si desea crear una función de extensión IEnumerable
public static void DisposeSequence<T>(this IEnumerable<T> source)
{
foreach (IDisposable disposableObject in source
.Where(source is System.IDisposable)
.Select(source as System.IDisposable) )
{
disposableObject.Dispose();
};
}
Todas las listas / diccionarios / listas de solo lectura / colecciones / etc. pueden usar estos métodos, ya que todos implementan la interfaz IEnumerable. Incluso puede usarlo si no todos los elementos de la secuencia implementan System.IDisposable.