vista por pasar parametros mvc metodo httppost enviar datos controlador c# .net events mvvm garbage-collection

c# - por - ¿Cómo elimino los controladores de eventos cuando termine con View y ViewModel, pero no con el modelo?



pasar datos de la vista al controlador mvc 4 (4)

En mi aplicación, a menudo estoy creando nuevas vistas y modelos de vista, pero persistiendo los mismos modelos. Por ejemplo, podría mostrar una vista simple de una lista de elementos en mi ventana principal y tener otra ventana con más detalles de cualquier elemento en particular. La ventana de detalles se puede abrir y cerrar en cualquier momento, o se pueden abrir varias ventanas simultáneamente para diferentes elementos de la lista.

Por lo tanto, puede haber más de un ViewModel para un objeto de modelo determinado, y deben actualizarse con cambios de otros lugares. (Estoy usando INotifyPropertyChanged en mis modelos). Quiero deshacerme de ViewModels cuando termine con ellos, es decir, ya que la ventana de detalles está cerrada.

public DetailViewModel(MyDetailModel detailModel) { // Retain the Detail Model this.model = detailModel; // Handle changes to the Model not coming from this ViewModel this.model.PropertyChanged += model_PropertyChanged; // Potential leak? }

Tengo entendido que el controlador de eventos hará que el Modelo retenga una referencia al ViewModel y evitará que se recolecte la basura.

1) ¿Es esto correcto? ¿Cómo puedo saber si estas referencias están todavía presentes?

2) ¿Cómo debo determinar que ViewModel ya no es necesario y anular la suscripción a los eventos?


Al principio pensé que este sería el camino a seguir:

public class DetailViewModel : IDisposable { public DetailViewModel(MyDetailModel detailModel) { // Retain the Detail Model this.model = detailModel; // Handle changes to the Model not coming from this ViewModel this.model.PropertyChanged += model_PropertyChanged; // Potential leak? } public void Dispose() { this.model.PropertyChanged -= model_PropertyChanged; } }

Pero entonces encontré esta hermosa pepita . Entonces, hay al menos dos soluciones posibles: (a) ejemplo de implementación de IDisposable , y (b) argumentos en contra de IDisposable . Te dejo el debate a ti. ;)

También puede considerar el patrón WeakEvent entre otros ...


Es posible que desee considerar el uso de un patrón de evento débil . Creo que Microsoft introdujo WeakEventManager y IWeakEventListener para resolver este problema exacto de recolección de basura.


Estoy siguiendo la respuesta de IAbstract , el WPF consiguió esto implementado directamente a través de PropertyChangedEventManager , se puede encontrar más there .

El código final podría verse como:

public class DetailViewModel : IDisposable { public DetailViewModel(MyDetailModel detailModel) { // Retain the Detail Model this.model = detailModel; // Handle changes to the Model not coming from this ViewModel if(model != null) PropertyChangedEventManager.AddHandler(model, model_PropertyChanged, ""); } public void Dispose() { if(model != null) PropertyChangedEventManager.RemoveHandler(model, model_PropertyChanged, ""); } }


Soy un gran fan de usar IDisposable para este tipo de cosas. De hecho, puede obtener excelentes resultados con un CompositeDisposable para manejar todas sus necesidades de limpieza.

Esto es lo que hago:

public class DetailViewModel : IDisposable { private readonly CompositeDisposable _disposables = new CompositeDisposable(); public void Dispose() { _disposables.Dispose(); } private readonly MyDetailModel _model; public DetailViewModel(MyDetailModel model) { _model = model; _model.PropertyChanged += _model_PropertyChanged; Action removeHandler = () => _model.PropertyChanged -= _model_PropertyChanged; _disposables.Add(removeHandler); } private void _model_PropertyChanged( object sender, PropertyChangedEventArgs e) { /* ... */ } }

Lo que esto le permite hacer es pegar todo tipo de código de limpieza en una colección que se ejecuta automáticamente una vez y solo una vez cuando se llama a IDisposable.Dispose() en su clase.

Esto es especialmente bueno para los controladores de eventos, ya que le permite colocar el código Agregar controlador a continuación para eliminar el código del controlador en su fuente y esto hace que la refactorización sea mucho más simple. Es muy fácil ver si realmente está eliminando controladores si el código está junto al controlador Agregar.

Para que esto suceda, necesitas agregar dos clases a tu código.

El primero es CompositeDisposable :

public sealed class CompositeDisposable : IEnumerable<IDisposable>, IDisposable { private readonly List<IDisposable> _disposables; private bool _disposed; public CompositeDisposable() { _disposables = new List<IDisposable>(); } public CompositeDisposable(IEnumerable<IDisposable> disposables) { if (disposables == null) { throw new ArgumentNullException("disposables"); } _disposables = new List<IDisposable>(disposables); } public CompositeDisposable(params IDisposable[] disposables) { if (disposables == null) { throw new ArgumentNullException("disposables"); } _disposables = new List<IDisposable>(disposables); } public void Add(IDisposable disposable) { if (disposable == null) { throw new ArgumentNullException("disposable"); } lock (_disposables) { if (_disposed) { disposable.Dispose(); } else { _disposables.Add(disposable); } } } public IDisposable Add(Action action) { if (action == null) { throw new ArgumentNullException("action"); } var disposable = new AnonymousDisposable(action); this.Add(disposable); return disposable; } public IDisposable Add<TDelegate>( Action<TDelegate> add, Action<TDelegate> remove, TDelegate handler) { if (add == null) { throw new ArgumentNullException("add"); } if (remove == null) { throw new ArgumentNullException("remove"); } if (handler == null) { throw new ArgumentNullException("handler"); } add(handler); return this.Add(() => remove(handler)); } public void Clear() { lock (_disposables) { var disposables = _disposables.ToArray(); _disposables.Clear(); Array.ForEach(disposables, d => d.Dispose()); } } public void Dispose() { lock (_disposables) { if (!_disposed) { this.Clear(); } _disposed = true; } } public IEnumerator<IDisposable> GetEnumerator() { lock (_disposables) { return _disposables.ToArray().AsEnumerable().GetEnumerator(); } } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public bool IsDisposed { get { return _disposed; } } }

Y el segundo, que se usa en CompositeDisposable , es AnonymousDisposable .

public sealed class AnonymousDisposable : IDisposable { private readonly Action _action; private int _disposed; public AnonymousDisposable(Action action) { _action = action; } public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 0) { _action(); } } }

La clase AnonymousDisposable se utiliza para convertir una Action en un IDisposable para que la acción se ejecute cuando el AnonymousDisposable esté dispuesto.

Otra opción que ahora puede usar fácilmente es el uso de controladores de eventos anónimos en lugar de tener que definir métodos privados para manejar los eventos.

Podrías usar esto en el constructor en su lugar:

PropertyChangedEventHandler handler = (s, e) => { // Use inline lambdas instead of private methods to handle events }; model.PropertyChanged += handler; _disposables.Add(() => model.PropertyChanged -= handler);

Puede usar variables de nivel de método en lamdbas, por lo que esta opción puede ayudar a que su módulo se desordene.

Ahora, podría detenerse en esto, pero podría haber notado otra sobrecarga de Add en la clase CompositeDisposable que ayuda a agregar suscripciones de eventos, como por ejemplo:

PropertyChangedEventHandler handler = (s, e) => { /* ... */ }; _disposables.Add( h => model.PropertyChanged += h, h => model.PropertyChanged -= h, handler);

Esto hace todo el trabajo de suscripción y cancelación de la suscripción del controlador.

Incluso puede ir un paso más allá y hacerlo todo en una línea, como esto:

_disposables.Add<PropertyChangedEventHandler>( h => model.PropertyChanged += h, h => model.PropertyChanged -= h, (s, e) => { // ... });

Dulce, ¿eh?

Espero que esto ayude.