atributo ObservableProperty

El ObservableProperty tipo es un atributo que permite generar propiedades observables a partir de campos anotados. Su propósito es reducir en gran medida la cantidad de código repetitivo necesaria para definir propiedades observables.

Note

Para poder funcionar, los campos anotados deben estar en una clase parcial con la infraestructura necesaria INotifyPropertyChanged . Si el tipo está anidado, todos los tipos del árbol de sintaxis de la declaración también deben marcarse 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, NotifyCanExecuteChangedForNotifyDataErrorInfo, NotifyPropertyChangedRecipientsICommand, IRelayCommand, ObservableValidator, PropertyChangedMessage<T>, ,IMessenger

Cómo funciona

El ObservableProperty atributo 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.

Note

El nombre de la propiedad generada se creará en función del nombre del campo. El generador asume que el campo se llama lowerCamel, _lowerCamel o m_lowerCamel, y lo transformará en UpperCamel para seguir las convenciones de nomenclatura correctas de .NET. La propiedad resultante siempre tendrá accesores públicos, pero el campo puede declararse 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 quieras ejecutar cierta lógica que solo necesite referirse al nuevo valor asignado a 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;
    }
}

Puede implementar cualquiera de los métodos disponibles, o ninguno. Si no se implementan (o si solo se implementa una de ellas), el compilador simplemente eliminará por completo las llamadas, por lo que no habrá ninguna penalización de rendimiento en los casos en que no se requiera esta funcionalidad adicional.

Note

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 simplemente como métodos partial, y siempre serán implícitamente privadas. 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 tiene una propiedad FullName de la que quisiera recibir una notificación cada vez que Name cambie. Puede hacerlo usando el atributo NotifyPropertyChangedFor, así:

[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 debe volver a activarse. Puede lograrlo mediante el NotifyCanExecuteChangedFor atributo :

[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 IRelayCommand propiedad.

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 NotifyDataErrorInfo atributo :

[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 ObservableValidator objeto para que los componentes de la interfaz de usuario puedan reaccionar a ella y mostrar los errores de validación correctamente.

Note

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 indicarle al generador que inserte también código para enviar un mensaje de cambio de propiedad cuando la propiedad cambie. 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 a Broadcast enviará un nuevo PropertyChangedMessage<T> a todos los suscriptores registrados mediante la instancia de IMessenger que se esté usando en el modelo de vista actual.

Agregar 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 de campos anotados, y MVVM Toolkit redirigirá 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")] aplicados a 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.