I wrote a short class that can synchronize properties for any collection of .NET objects. The type needs to implement INotifyPropertyChanged, but other than that there is no special requirements to allow a type to be synchronized with this mechanism.
The full class definition follows. A usage example would be give, a class Car with a writeable property MaxSpeed, a synchronizer can be created like this:
The full class definition follows. A usage example would be give, a class Car with a writeable property MaxSpeed, a synchronizer can be created like this:
new PropertySynchronizerthen a collection of objects of class Car can be added using AddRange; all of these will have there MaxSpeed synchronized to the first items MaxSpeed. Any change to one of the them will change all of them.(car => car.MaxSpeed)
////// span="""T" the type of the object to synchronize /// span="">"PropertyType" the type of the Property to be synchronized public class PropertySynchronizerwhere T : INotifyPropertyChanged {
Note that the constraint where T : INotifyPropertyChanged can be relaxed; if INotifyPropertyChanged is not implemented then there will not be updates for the class changing.
/// constructs a Synchronizer for object type T and PropertyType.
/// expression provided to identify the property /// "forMemberLambda" expression of the form 'obj => obj.Property'
public PropertySynchronizer(Expression<Func> forMemberLambda)
{
// store the member expression and compiled function
_forMemberLambda = forMemberLambda;
_forMember = _forMemberLambda.Compile();
// test to get the member info
LambdaExpression lambdaExpression = (LambdaExpression)_forMemberLambda;
MemberExpression memberExpression = (MemberExpression)lambdaExpression.Body;
_memberInfo = memberExpression.Member;
}
Expression<Func> _forMemberLambda;
Func _forMember;
MemberInfo _memberInfo;
/// "newObject" the collection of objects public void AddRange(IEnumerable<T> The constructor takes an expression representing the member property, and uses it to extract the MemberInfo as well as to create a compiled function. Both are used to access the property./// adds a collection of objects to be synchronizednewObjects) { // for each of the objects in the collection... foreach (T newObject in newObjects) { // if it is not the first object if (_synchronizedObjects.Count() > 0) { // then set the property value SetPropertyValue(newObject, _forMember(_synchronizedObjects.First())); } // set up the property change event INotifyPropertyChanged notifyPropertyChanged =
newObject as INotifyPropertyChanged; if (notifyPropertyChanged != null) { notifyPropertyChanged.PropertyChanged +=
(s, e) => OnSyncObjectPropertyChanged(s, e); } // add the object to the list _synchronizedObjects.Add(newObject); } } List_synchronizedObjects = new List AddRange adds a collection of type T objects, setting the property values and also adding the change event handlers();
/// removes all objects from synchronization public void RemoveAll() { // iterate over objects foreach (T existingObject in _synchronizedObjects) { // remove property change event handler INotifyPropertyChanged notifyPropertyChanged =
existingObject as INotifyPropertyChanged; if (notifyPropertyChanged != null) { notifyPropertyChanged.PropertyChanged -=
(s, e) => OnSyncObjectPropertyChanged(s, e);
}
}
// clear the collection
_synchronizedObjects.Clear();
}
RemoveAll removes all synchronized objects from the collection/// sets the property value for an object /// "forObject" object of type T whose property is to be set /// "value" the value to set public void SetPropertyValue(T forObject, PropertyType value) { // standard flags for property invoke BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty; // set the member value _memberInfo.DeclaringType.InvokeMember(_memberInfo.Name, flags, null, forObject, new object[] { value }); }
SetPropertyValue sets the property value for a given T object using InvokeMember/// respond to property change /// "sender" the object originating the change /// "args" change event args public void OnSyncObjectPropertyChanged(object sender, PropertyChangedEventArgs args) { // check that the property of interest is the one being triggered if (args.PropertyName.CompareTo(_memberInfo.Name) == 0) { // are we already updating this property? then skip if (_performingUpdate) return; // set flag to prevent recursion _performingUpdate = true; // get the value to be set PropertyType value = _forMember((T)sender); foreach (T otherObject in _synchronizedObjects) { if (!otherObject.Equals((T)sender)) { SetPropertyValue(otherObject, value); } } // done with update _performingUpdate = false; } } // flag to ensure that we don't recurse bool _performingUpdate = false; }
OnSyncObjectPropertyChanged responds to property changes by updating the other objects in the collection to have the same property value as the sender of the change event.