// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. /// /// A base class for objects of which the properties must be observable. /// public abstract class ObservableObject : global::System.ComponentModel.INotifyPropertyChanged, global::System.ComponentModel.INotifyPropertyChanging { /// public event global::System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; /// public event global::System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; /// /// Raises the event. /// /// The input instance. protected virtual void OnPropertyChanged(global::System.ComponentModel.PropertyChangedEventArgs e) { PropertyChanged?.Invoke(this, e); } /// /// Raises the event. /// /// The input instance. protected virtual void OnPropertyChanging(global::System.ComponentModel.PropertyChangingEventArgs e) { PropertyChanging?.Invoke(this, e); } /// /// Raises the event. /// /// (optional) The name of the property that changed. protected void OnPropertyChanged([global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { OnPropertyChanged(new global::System.ComponentModel.PropertyChangedEventArgs(propertyName)); } /// /// Raises the event. /// /// (optional) The name of the property that changed. protected void OnPropertyChanging([global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { OnPropertyChanging(new global::System.ComponentModel.PropertyChangingEventArgs(propertyName)); } /// /// Compares the current and new values for a given property. If the value has changed, /// raises the event, updates the property with the new /// value, then raises the event. /// /// The type of the property that changed. /// The field storing the property's value. /// The property's value after the change occurred. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// /// The and events are not raised /// if the current and new value for the target property are the same. /// protected bool SetProperty([global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("newValue")] ref T field, T newValue, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { if (global::System.Collections.Generic.EqualityComparer.Default.Equals(field, newValue)) { return false; } OnPropertyChanging(propertyName); field = newValue; OnPropertyChanged(propertyName); return true; } /// /// Compares the current and new values for a given property. If the value has changed, /// raises the event, updates the property with the new /// value, then raises the event. /// See additional notes about this overload in . /// /// The type of the property that changed. /// The field storing the property's value. /// The property's value after the change occurred. /// The instance to use to compare the input values. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. protected bool SetProperty([global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("newValue")] ref T field, T newValue, global::System.Collections.Generic.IEqualityComparer comparer, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { if (comparer.Equals(field, newValue)) { return false; } OnPropertyChanging(propertyName); field = newValue; OnPropertyChanged(propertyName); return true; } /// /// Compares the current and new values for a given property. If the value has changed, /// raises the event, updates the property with the new /// value, then raises the event. /// This overload is much less efficient than and it /// should only be used when the former is not viable (eg. when the target property being /// updated does not directly expose a backing field that can be passed by reference). /// For performance reasons, it is recommended to use a stateful callback if possible through /// the whenever possible /// instead of this overload, as that will allow the C# compiler to cache the input callback and /// reduce the memory allocations. More info on that overload are available in the related XML /// docs. This overload is here for completeness and in cases where that is not applicable. /// /// The type of the property that changed. /// The current property value. /// The property's value after the change occurred. /// A callback to invoke to update the property value. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// /// The and events are not raised /// if the current and new value for the target property are the same. /// protected bool SetProperty(T oldValue, T newValue, global::System.Action callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { if (global::System.Collections.Generic.EqualityComparer.Default.Equals(oldValue, newValue)) { return false; } OnPropertyChanging(propertyName); callback(newValue); OnPropertyChanged(propertyName); return true; } /// /// Compares the current and new values for a given property. If the value has changed, /// raises the event, updates the property with the new /// value, then raises the event. /// See additional notes about this overload in . /// /// The type of the property that changed. /// The current property value. /// The property's value after the change occurred. /// The instance to use to compare the input values. /// A callback to invoke to update the property value. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. protected bool SetProperty(T oldValue, T newValue, global::System.Collections.Generic.IEqualityComparer comparer, global::System.Action callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { if (comparer.Equals(oldValue, newValue)) { return false; } OnPropertyChanging(propertyName); callback(newValue); OnPropertyChanged(propertyName); return true; } /// /// Compares the current and new values for a given nested property. If the value has changed, /// raises the event, updates the property and then raises the /// event. The behavior mirrors that of , /// with the difference being that this method is used to relay properties from a wrapped model in the /// current instance. This type is useful when creating wrapping, bindable objects that operate over /// models that lack support for notification (eg. for CRUD operations). /// Suppose we have this model (eg. for a database row in a table): /// /// public class Person /// { /// public string Name { get; set; } /// } /// /// We can then use a property to wrap instances of this type into our observable model (which supports /// notifications), injecting the notification to the properties of that model, like so: /// /// [ObservableObject] /// public class BindablePerson /// { /// public Model { get; } /// /// public BindablePerson(Person model) /// { /// Model = model; /// } /// /// public string Name /// { /// get => Model.Name; /// set => Set(Model.Name, value, Model, (model, name) => model.Name = name); /// } /// } /// /// This way we can then use the wrapping object in our application, and all those "proxy" properties will /// also raise notifications when changed. Note that this method is not meant to be a replacement for /// , and it should only be used when relaying properties to a model that /// doesn't support notifications, and only if you can't implement notifications to that model directly (eg. by having /// it inherit from ). The syntax relies on passing the target model and a stateless callback /// to allow the C# compiler to cache the function, which results in much better performance and no memory usage. /// /// The type of model whose property (or field) to set. /// The type of property (or field) to set. /// The current property value. /// The property's value after the change occurred. /// The model containing the property being updated. /// The callback to invoke to set the target property value, if a change has occurred. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// /// The and events are not /// raised if the current and new value for the target property are the same. /// protected bool SetProperty(T oldValue, T newValue, TModel model, global::System.Action callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) where TModel : class { if (global::System.Collections.Generic.EqualityComparer.Default.Equals(oldValue, newValue)) { return false; } OnPropertyChanging(propertyName); callback(model, newValue); OnPropertyChanged(propertyName); return true; } /// /// Compares the current and new values for a given nested property. If the value has changed, /// raises the event, updates the property and then raises the /// event. The behavior mirrors that of , /// with the difference being that this method is used to relay properties from a wrapped model in the /// current instance. See additional notes about this overload in . /// /// The type of model whose property (or field) to set. /// The type of property (or field) to set. /// The current property value. /// The property's value after the change occurred. /// The instance to use to compare the input values. /// The model containing the property being updated. /// The callback to invoke to set the target property value, if a change has occurred. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. protected bool SetProperty(T oldValue, T newValue, global::System.Collections.Generic.IEqualityComparer comparer, TModel model, global::System.Action callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) where TModel : class { if (comparer.Equals(oldValue, newValue)) { return false; } OnPropertyChanging(propertyName); callback(model, newValue); OnPropertyChanged(propertyName); return true; } /// /// Compares the current and new values for a given field (which should be the backing /// field for a property). If the value has changed, raises the /// event, updates the field and then raises the event. /// The behavior mirrors that of , with the difference being that /// this method will also monitor the new value of the property (a generic ) and will also /// raise the again for the target property when it completes. /// This can be used to update bindings observing that or any of its properties. /// This method and its overload specifically rely on the type, which needs /// to be used in the backing field for the target property. The field doesn't need to be /// initialized, as this method will take care of doing that automatically. The /// type also includes an implicit operator, so it can be assigned to any instance directly. /// Here is a sample property declaration using this method: /// /// private TaskNotifier myTask; /// /// public Task MyTask /// { /// get => myTask; /// private set => SetAndNotifyOnCompletion(ref myTask, value); /// } /// /// /// The field notifier to modify. /// The property's value after the change occurred. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// /// The and events are not raised if the current /// and new value for the target property are the same. The return value being only /// indicates that the new value being assigned to is different than the previous one, /// and it does not mean the new instance passed as argument is in any particular state. /// protected bool SetPropertyAndNotifyOnCompletion([global::System.Diagnostics.CodeAnalysis.NotNull] ref TaskNotifier? taskNotifier, global::System.Threading.Tasks.Task? newValue, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, static _ => { }, propertyName); } /// /// Compares the current and new values for a given field (which should be the backing /// field for a property). If the value has changed, raises the /// event, updates the field and then raises the event. /// This method is just like , /// with the difference being an extra parameter with a callback being invoked /// either immediately, if the new task has already completed or is , or upon completion. /// /// The field notifier to modify. /// The property's value after the change occurred. /// A callback to invoke to update the property value. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// /// The and events are not raised /// if the current and new value for the target property are the same. /// protected bool SetPropertyAndNotifyOnCompletion([global::System.Diagnostics.CodeAnalysis.NotNull] ref TaskNotifier? taskNotifier, global::System.Threading.Tasks.Task? newValue, global::System.Action callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, callback, propertyName); } /// /// Compares the current and new values for a given field (which should be the backing /// field for a property). If the value has changed, raises the /// event, updates the field and then raises the event. /// The behavior mirrors that of , with the difference being that /// this method will also monitor the new value of the property (a generic ) and will also /// raise the again for the target property when it completes. /// This can be used to update bindings observing that or any of its properties. /// This method and its overload specifically rely on the type, which needs /// to be used in the backing field for the target property. The field doesn't need to be /// initialized, as this method will take care of doing that automatically. The /// type also includes an implicit operator, so it can be assigned to any instance directly. /// Here is a sample property declaration using this method: /// /// private TaskNotifier<int> myTask; /// /// public Task<int> MyTask /// { /// get => myTask; /// private set => SetAndNotifyOnCompletion(ref myTask, value); /// } /// /// /// The type of result for the to set and monitor. /// The field notifier to modify. /// The property's value after the change occurred. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// /// The and events are not raised if the current /// and new value for the target property are the same. The return value being only /// indicates that the new value being assigned to is different than the previous one, /// and it does not mean the new instance passed as argument is in any particular state. /// protected bool SetPropertyAndNotifyOnCompletion([global::System.Diagnostics.CodeAnalysis.NotNull] ref TaskNotifier? taskNotifier, global::System.Threading.Tasks.Task? newValue, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, static _ => { }, propertyName); } /// /// Compares the current and new values for a given field (which should be the backing /// field for a property). If the value has changed, raises the /// event, updates the field and then raises the event. /// This method is just like , /// with the difference being an extra parameter with a callback being invoked /// either immediately, if the new task has already completed or is , or upon completion. /// /// The type of result for the to set and monitor. /// The field notifier to modify. /// The property's value after the change occurred. /// A callback to invoke to update the property value. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// /// The and events are not raised /// if the current and new value for the target property are the same. /// protected bool SetPropertyAndNotifyOnCompletion([global::System.Diagnostics.CodeAnalysis.NotNull] ref TaskNotifier? taskNotifier, global::System.Threading.Tasks.Task? newValue, global::System.Action?> callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, callback, propertyName); } /// /// Implements the notification logic for the related methods. /// /// The type of to set and monitor. /// The field notifier. /// The property's value after the change occurred. /// A callback to invoke to update the property value. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. private bool SetPropertyAndNotifyOnCompletion(ITaskNotifier taskNotifier, TTask? newValue, global::System.Action callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) where TTask : global::System.Threading.Tasks.Task { if (ReferenceEquals(taskNotifier.Task, newValue)) { return false; } bool isAlreadyCompletedOrNull = newValue?.IsCompleted ?? true; OnPropertyChanging(propertyName); taskNotifier.Task = newValue; OnPropertyChanged(propertyName); if (isAlreadyCompletedOrNull) { callback(newValue); return true; } async void MonitorTask() { try { await newValue!; } catch { } if (ReferenceEquals(taskNotifier.Task, newValue)) { OnPropertyChanged(propertyName); } callback(newValue); } MonitorTask(); return true; } /// /// An interface for task notifiers of a specified type. /// /// The type of value to store. private interface ITaskNotifier where TTask : global::System.Threading.Tasks.Task { /// /// Gets or sets the wrapped value. /// TTask? Task { get; set; } } /// /// A wrapping class that can hold a value. /// protected sealed class TaskNotifier : ITaskNotifier { /// /// Initializes a new instance of the class. /// internal TaskNotifier() { } private global::System.Threading.Tasks.Task? task; /// global::System.Threading.Tasks.Task? ITaskNotifier.Task { get => this.task; set => this.task = value; } /// /// Unwraps the value stored in the current instance. /// /// The input instance. public static implicit operator global::System.Threading.Tasks.Task?(TaskNotifier? notifier) { return notifier?.task; } } /// /// A wrapping class that can hold a value. /// /// The type of value for the wrapped instance. protected sealed class TaskNotifier : ITaskNotifier> { /// /// Initializes a new instance of the class. /// internal TaskNotifier() { } private global::System.Threading.Tasks.Task? task; /// global::System.Threading.Tasks.Task? ITaskNotifier>.Task { get => this.task; set => this.task = value; } /// /// Unwraps the value stored in the current instance. /// /// The input instance. public static implicit operator global::System.Threading.Tasks.Task?(TaskNotifier? notifier) { return notifier?.task; } } }