/// <summary> /// Sets a property value and raises the <see cref="PropertyChanged"/> event where applicable. /// </summary> /// <typeparam name="T">The property <see cref="Type"/>.</typeparam> /// <param name="propertyValue">The property value to set.</param> /// <param name="setValue">The value to set.</param> /// <param name="immutable">Indicates whether the value is immutable; can not be changed once set.</param> /// <param name="bubblePropertyChanged">Indicates whether the value should bubble up property changes versus only recording within the sub-entity itself.</param> /// <param name="beforeChange">Function to invoke before changing the value; a result of <c>true</c> indicates that the property change is to be cancelled; otherwise, <c>false</c>.</param> /// <param name="propertyNames">The names of the properties that changed.</param> /// <returns><c>true</c> indicates that the property value changed; otherwise, <c>false</c>.</returns> /// <remarks>The first property name specified (see <paramref name="propertyNames"/>) is the primary property; therefore, the only property /// where the <see cref="BeforePropertyChanged"/> event is raised. The additional property names allow for the <see cref="PropertyChanged"/> /// event to be raised for other properties where related versus having to raise seperately.</remarks> protected bool SetValue <T>(ref T propertyValue, T setValue, bool immutable = false, bool bubblePropertyChanged = false, Func <T, bool>?beforeChange = null, params string[] propertyNames) { ValidateSetValuePropertyNames(propertyNames); lock (_lock) { // Check and see if the value has changed or not; exit if being set to same value. var isChanged = true; T val = Cleaner.Clean(setValue); if (propertyValue is IComparable <T> ) { if (Comparer <T> .Default.Compare(val, propertyValue) == 0) { isChanged = false; } } else if (Equals(propertyValue, val)) { isChanged = false; } if (!isChanged && !RaisePropertyChangedWhenSame) { return(false); } // Test is read only. if (IsReadOnly) { return(!isChanged ? false : throw new InvalidOperationException(EntityIsReadOnlyMessage)); } // Test immutability. if (immutable && isChanged && Comparer <T> .Default.Compare(propertyValue, default !) != 0) { throw new InvalidOperationException(ValueIsImmutableMessage); } // Handle on before property changed. if (beforeChange != null) { if (beforeChange.Invoke(val)) { return(false); } } if (OnBeforePropertyChanged(propertyNames[0], val)) { return(false); } // Determine bubbling and unwire old value. INotifyPropertyChanged?npc; if (bubblePropertyChanged && propertyValue != null) { npc = propertyValue as INotifyPropertyChanged; if (npc != null) { npc.PropertyChanged -= GetValue_PropertyChanged(propertyNames); } } // Track changes for the new value where parent (this) is tracking. if (this is IChangeTrackingLogging ct && ct.IsChangeTracking && val is IChangeTrackingLogging sct && !sct.IsChangeTracking) { sct.TrackChanges(); } // Update the property and trigger the property changed. propertyValue = val; TriggerPropertyChanged(propertyNames); // Determine bubbling and wire up new value. if (bubblePropertyChanged && val != null) { npc = val as INotifyPropertyChanged; if (npc != null) { npc.PropertyChanged += GetValue_PropertyChanged(propertyNames); } } return(true); } }