Created
April 20, 2020 11:59
-
-
Save nesteruk/8bb35be189fb127ce7f949691b668e8a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.ComponentModel; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
using System.Runtime.CompilerServices; | |
using DotNetDesignPatternDemos.Annotations; | |
namespace DotNetDesignPatternDemos.Behavioral.Observer.Bidirectional | |
{ | |
public class Product : INotifyPropertyChanged | |
{ | |
private string name; | |
public string Name | |
{ | |
get => name; | |
set | |
{ | |
if (value == name) return; // critical | |
name = value; | |
OnPropertyChanged(); | |
} | |
} | |
public event PropertyChangedEventHandler PropertyChanged; | |
[NotifyPropertyChangedInvocator] | |
protected virtual void OnPropertyChanged( | |
[CallerMemberName] string propertyName = null) | |
{ | |
PropertyChanged?.Invoke(this, | |
new PropertyChangedEventArgs(propertyName)); | |
} | |
public override string ToString() | |
{ | |
return $"Product: {Name}"; | |
} | |
} | |
public class Window : INotifyPropertyChanged | |
{ | |
private string productName; | |
public string ProductName | |
{ | |
get => productName; | |
set | |
{ | |
if (value == productName) return; // critical | |
productName = value; | |
OnPropertyChanged(); | |
} | |
} | |
public event PropertyChangedEventHandler PropertyChanged; | |
[NotifyPropertyChangedInvocator] | |
protected virtual void OnPropertyChanged( | |
[CallerMemberName] string propertyName = null) | |
{ | |
PropertyChanged?.Invoke(this, | |
new PropertyChangedEventArgs(propertyName)); | |
} | |
public override string ToString() | |
{ | |
return $"Window: {ProductName}"; | |
} | |
} | |
public sealed class BidirectionalBinding : IDisposable | |
{ | |
private bool disposed; | |
public BidirectionalBinding( | |
INotifyPropertyChanged first, | |
Expression<Func<object>> firstProperty, | |
INotifyPropertyChanged second, | |
Expression<Func<object>> secondProperty) | |
{ | |
if (firstProperty.Body is MemberExpression firstExpr | |
&& secondProperty.Body is MemberExpression secondExpr) | |
{ | |
if (firstExpr.Member is PropertyInfo firstProp | |
&& secondExpr.Member is PropertyInfo secondProp) | |
{ | |
first.PropertyChanged += (sender, args) => | |
{ | |
if (!disposed) | |
{ | |
secondProp.SetValue(second, firstProp.GetValue(first)); | |
} | |
}; | |
second.PropertyChanged += (sender, args) => | |
{ | |
if (!disposed) | |
{ | |
firstProp.SetValue(first, secondProp.GetValue(second)); | |
} | |
}; | |
} | |
} | |
} | |
public void Dispose() | |
{ | |
disposed = true; | |
} | |
} | |
public class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
var product = new Product{Name="Book"}; | |
var window = new Window{ProductName = "Book"}; | |
// want to ensure that when product name changes | |
// in one component, it also changes in another | |
product.PropertyChanged += (sender, eventArgs) => | |
{ | |
if (eventArgs.PropertyName == "Name") | |
{ | |
Console.WriteLine("Name changed in Product"); | |
window.ProductName = product.Name; | |
} | |
}; | |
window.PropertyChanged += (sender, eventArgs) => | |
{ | |
if (eventArgs.PropertyName == "ProductName") | |
{ | |
Console.WriteLine("Name changed in Window"); | |
product.Name = window.ProductName; | |
} | |
}; | |
using var binding = new BidirectionalBinding( | |
product, | |
() => product.Name, | |
window, | |
() => window.ProductName); | |
// there is no infinite loop because of | |
// self-assignment guard | |
product.Name = "Table"; | |
window.ProductName = "Chair"; | |
Console.WriteLine(product); | |
Console.WriteLine(window); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment