Implementación de INotifyPropertyChanged: ¿existe una forma mejor?
Microsoft debería haber implementado algo ágil INotifyPropertyChanged
, como en las propiedades automáticas, solo especifique. {get; set; notify;}
Creo que tiene mucho sentido hacerlo. ¿O hay alguna complicación para hacerlo?
¿Podemos implementar nosotros mismos algo como 'notificar' en nuestras propiedades? ¿Existe una solución elegante para implementar INotifyPropertyChanged
en su clase o la única forma de hacerlo es generando el PropertyChanged
evento en cada propiedad?
Si no, ¿podemos escribir algo para generar automáticamente el fragmento de código para generar el PropertyChanged
evento?
Sin usar algo como postsharp, la versión mínima que uso usa algo como:
public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
Entonces, cada propiedad es algo así como:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
que no es enorme; también se puede utilizar como clase base si lo desea. El bool
retorno de SetField
le indica si fue una operación no operativa, en caso de que desee aplicar otra lógica.
o incluso más fácil con C# 5:
protected bool SetField<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{...}
que se puede llamar así:
set { SetField(ref name, value); }
con lo cual el compilador agregará "Name"
automáticamente.
C# 6.0 facilita la implementación:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
...y ahora con C#7:
protected void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private string name;
public string Name
{
get => name;
set => SetField(ref name, value);
}
Y, con C# 8 y tipos de referencia anulables, se vería así:
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private string name;
public string Name
{
get => name;
set => SetField(ref name, value);
}
.Net 4.5 introdujo los atributos de información de la persona que llama:
private void OnPropertyChanged<T>([CallerMemberName]string caller = null) {
// make sure only to call this if the value actually changes
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(caller));
}
}
Probablemente también sea una buena idea agregar un comparador a la función.
EqualityComparer<T>.Default.Equals
Más ejemplos aquí y aquí
Consulte también Información de la persona que llama (C# y Visual Basic)