Compartir a través de


Atributo ObservableProperty

El tipo ObservableProperty es un atributo que permite generar propiedades observables a partir de campos anotados. Su propósito es reducir considerablemente la cantidad de repeticiones que se necesitan para definir propiedades observables.

Nota:

Para poder funcionar, los campos anotados deben estar en una clase parcial con la infraestructura INotifyPropertyChanged necesaria. Si el tipo está anidado, todos los tipos del árbol de sintaxis de declaración también se deben anotar como parciales. Si no lo hace, se producirán errores de compilación, ya que el generador no podrá generar una declaración parcial diferente de ese tipo con la propiedad observable solicitada.

API de plataforma:ObservableProperty, NotifyPropertyChangedFor, NotifyCanExecuteChangedFor, NotifyDataErrorInfo, NotifyPropertyChangedRecipients, ICommand, IRelayCommand, ObservableValidator, PropertyChangedMessage<T>, IMessenger

Funcionamiento

El atributo ObservableProperty se puede usar para anotar un campo en un tipo parcial, de la siguiente manera:

[ObservableProperty]
private string? name;

Y generará una propiedad observable como esta:

public string? Name
{
    get => name;
    set => SetProperty(ref name, value);
}

También lo hará con una implementación optimizada, por lo que el resultado final será aún más rápido.

Nota:

El nombre de la propiedad generada se creará en función del nombre del campo. El generador supone que el campo se denomina lowerCamel, _lowerCamel o m_lowerCamel, y transformará lo transformará a UpperCamel para seguir las convenciones de nomenclatura de .NET adecuadas. La propiedad resultante siempre tendrá descriptores de acceso públicos, pero el campo se puede declarar con cualquier visibilidad (se recomienda private).

Ejecución de código tras cambios

El código generado es realmente un poco más complejo que este, y el motivo de esto es que también expone algunos métodos que puede implementar para enlazar a la lógica de notificación y ejecutar lógica adicional cuando la propiedad está a punto de actualizarse y justo después de actualizarla, si es necesario. Es decir, el código generado es realmente similar al siguiente:

public string? Name
{
    get => name;
    set
    {
        if (!EqualityComparer<string?>.Default.Equals(name, value))
        {
            string? oldValue = name;
            OnNameChanging(value);
            OnNameChanging(oldValue, value);
            OnPropertyChanging();
            name = value;
            OnNameChanged(value);
            OnNameChanged(oldValue, value);
            OnPropertyChanged();
        }
    }
}

partial void OnNameChanging(string? value);
partial void OnNameChanged(string? value);

partial void OnNameChanging(string? oldValue, string? newValue);
partial void OnNameChanged(string? oldValue, string? newValue);

Esto le permite implementar cualquiera de esos métodos para insertar código adicional. Los dos primeros son útiles siempre que quiera ejecutar alguna lógica que solo necesite hacer referencia al nuevo valor en el que se ha establecido la propiedad. Los otros dos son útiles siempre que tenga una lógica más compleja que también tenga que actualizar algún estado en el valor anterior y nuevo que se establece.

Por ejemplo, este es un ejemplo de cómo se pueden usar las dos primeras sobrecargas:

[ObservableProperty]
private string? name;

partial void OnNameChanging(string? value)
{
    Console.WriteLine($"Name is about to change to {value}");
}

partial void OnNameChanged(string? value)
{
    Console.WriteLine($"Name has changed to {value}");
}

Y este es un ejemplo de cómo se pueden usar las otras dos sobrecargas:

[ObservableProperty]
private ChildViewModel? selectedItem;

partial void OnSelectedItemChanging(ChildViewModel? oldValue, ChildViewModel? newValue)
{
    if (oldValue is not null)
    {
        oldValue.IsSelected = true;
    }

    if (newValue is not null)
    {
        newValue.IsSelected = true;
    }
}

Solo tiene la libertad de implementar cualquier número de métodos entre los que están disponibles o ninguno de ellos. Si no se implementan (o si solo hay uno), el compilador solo quitará todas las llamadas, por lo que no habrá ningún impacto de rendimiento en todos los casos en los que no se requiera esta funcionalidad adicional.

Nota:

Los métodos generados son métodos parciales sin implementación, lo que significa que si decide implementarlos, no puede especificar una accesibilidad explícita para ellos. Es decir, las implementaciones de estos métodos también deben declararse solo como métodos partial, y siempre tendrán implícitamente accesibilidad privada. Al intentar agregar una accesibilidad explícita (por ejemplo, agregar public o private) se producirá un error, ya que no se permite en C#.

Notificación de propiedades dependientes

Imagine que tenía una propiedad FullName para la que quería generar una notificación cada vez que Name cambia. Puede hacerlo mediante el atributo NotifyPropertyChangedFor, de la siguiente manera:

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
private string? name;

Esto dará como resultado una propiedad generada equivalente a esta:

public string? Name
{
    get => name;
    set
    {
        if (SetProperty(ref name, value))
        {
            OnPropertyChanged("FullName");
        }
    }
}

Notificación de comandos dependientes

Imagine que tenía un comando cuyo estado de ejecución dependía del valor de esta propiedad. Es decir, siempre que la propiedad cambie, el estado de ejecución del comando se debe invalidar y calcular de nuevo. En otras palabras, ICommand.CanExecuteChanged se debe volver a generar. Puede lograrlo mediante el atributo NotifyCanExecuteChangedFor:

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(MyCommand))]
private string? name;

Esto dará como resultado una propiedad generada equivalente a esta:

public string? Name
{
    get => name;
    set
    {
        if (SetProperty(ref name, value))
        {
            MyCommand.NotifyCanExecuteChanged();
        }
    }
}

Para que esto funcione, el comando de destino debe ser alguna propiedad IRelayCommand.

Solicitud de validación de propiedades

Si la propiedad se declara en un tipo que hereda de ObservableValidator, también es posible anotarla con cualquier atributo de validación y, a continuación, solicitar al establecedor generado que desencadene la validación de esa propiedad. Esto se puede lograr con el atributo NotifyDataErrorInfo:

[ObservableProperty]
[NotifyDataErrorInfo]
[Required]
[MinLength(2)] // Any other validation attributes too...
private string? name;

Esto hará que se genere la siguiente propiedad:

public string? Name
{
    get => name;
    set
    {
        if (SetProperty(ref name, value))
        {
            ValidateProperty(value, "Value2");
        }
    }
}

Esa llamada generada ValidateProperty validará la propiedad y actualizará el estado del objeto ObservableValidator para que los componentes de la interfaz de usuario puedan reaccionar a ella y mostrar los errores de validación correctamente.

Nota:

Por diseño, solo los atributos de campo que heredan de ValidationAttribute se reenviarán a la propiedad generada. Esto se hace específicamente para admitir escenarios de validación de datos. Todos los demás atributos de campo se omitirán, por lo que actualmente no es posible agregar atributos personalizados adicionales en un campo y tenerlos también aplicados a la propiedad generada. Si es necesario (por ejemplo, para controlar la serialización), considere la posibilidad de usar una propiedad manual tradicional en su lugar.

Envío de mensajes de notificación

Si la propiedad se declara en un tipo que hereda de ObservableRecipient, puede usar el atributo NotifyPropertyChangedRecipients para indicar al generador que también inserte código para enviar un mensaje cambiado de propiedad para el cambio de propiedad. Esto permitirá que los destinatarios registrados reaccionen dinámicamente al cambio. Es decir, tenga en cuenta este código:

[ObservableProperty]
[NotifyPropertyChangedRecipients]
private string? name;

Esto hará que se genere la siguiente propiedad:

public string? Name
{
    get => name;
    set
    {
        string? oldValue = name;

        if (SetProperty(ref name, value))
        {
            Broadcast(oldValue, value);
        }
    }
}

Después, esa llamada generada Broadcast enviará un nuevo PropertyChangedMessage<T> que usa la instancia IMessenger en uso en el modelo de vista actual a todos los suscriptores registrados.

Adición de atributos personalizados

En algunos casos, puede resultar útil tener también algunos atributos personalizados sobre las propiedades generadas. Para lograrlo, simplemente puede usar el destino [property: ] en las listas de atributos sobre campos anotados y el kit de herramientas de MVVM reenvía automáticamente esos atributos a las propiedades generadas.

Por ejemplo, considere un campo similar al siguiente:

[ObservableProperty]
[property: JsonRequired]
[property: JsonPropertyName("name")]
private string? username;

Esto generará una propiedad Username, con esos dos atributos [JsonRequired] y [JsonPropertyName("name")] sobre ella. Puede usar tantas listas de atributos que tienen como destino la propiedad como desee y todas ellas se reenviarán a las propiedades generadas.

Ejemplos

  • Consulte la aplicación de ejemplo (para varios marcos de interfaz de usuario) para ver el kit de herramientas de MVVM en acción.
  • También puede encontrar más ejemplos en las pruebas unitarias.