Edited at

式木によるBindableBase

More than 1 year has passed since last update.

最近思いついた、けどきっと誰かやってると思うアイディア。

よくあるBindableBaseはこんな感じ


public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value)) return false;

storage = value;
OnPropertyChanged(propertyName);

return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

問題点はプロパティはrefで使えないこと。

幸せになるためにこんなクラスを追加して(From ReactiveProperty)

public static class AccessorCache<TType>
{
private static readonly Dictionary<string, Delegate> getCache = new Dictionary<string, Delegate>();
private static readonly Dictionary<string, Delegate> setCache = new Dictionary<string, Delegate>();

public static Func<TType, TProperty> LookupGet<TProperty>(Expression<Func<TType, TProperty>> propertySelector)
{
var propertyName = GetPropertyName(propertySelector);
Delegate accessor;

lock (getCache)
{
if (!getCache.TryGetValue(propertyName, out accessor))
{
accessor = propertySelector.Compile();
getCache.Add(propertyName, accessor);
}
}

return (Func<TType, TProperty>)accessor;
}

private static string GetPropertyName<TProperty>(Expression<Func<TType, TProperty>> propertySelector)
{
var memberExpression = propertySelector.Body as MemberExpression;
if (memberExpression == null)
{
var unaryExpression = propertySelector.Body as UnaryExpression;
if (unaryExpression == null) { throw new ArgumentException(nameof(propertySelector)); }
memberExpression = unaryExpression.Operand as MemberExpression;
if (memberExpression == null) { throw new ArgumentException(nameof(propertySelector)); }
}

return memberExpression.Member.Name;
}

public static Action<TType, TProperty> LookupSet<TProperty>(Expression<Func<TType, TProperty>> propertySelector)
{
var propertyName = GetPropertyName(propertySelector);
Delegate accessor;

lock (setCache)
{
if (!setCache.TryGetValue(propertyName, out accessor))
{
accessor = CreateSetAccessor(propertySelector);
setCache.Add(propertyName, accessor);
}
}

return (Action<TType, TProperty>)accessor;
}

private static Delegate CreateSetAccessor<TProperty>(Expression<Func<TType, TProperty>> propertySelector)
{
var propertyInfo = (PropertyInfo)((MemberExpression)propertySelector.Body).Member;
var selfParameter = Expression.Parameter(typeof(TType), "self");
var valueParameter = Expression.Parameter(typeof(TProperty), "value");
var body = Expression.Assign(Expression.Property(selfParameter, propertyInfo), valueParameter);
var lambda = Expression.Lambda<Action<TType, TProperty>>(body, selfParameter, valueParameter);
return lambda.Compile();
}
}

こんなメソッドを作ってみる


protected bool SetProperty<TType, TProperty>(TType targetClass, Expression<Func<TType, TProperty>> selector, TProperty value, [CallerMemberName] String propertyName = null)
{
var oldValue = AccessorCache<TType>.LookupGet(selector).Invoke(targetClass);

if (object.Equals(oldValue, value)) return false;

AccessorCache<TType>.LookupSet(selector).Invoke(targetClass, value);
this.OnPropertyChanged(propertyName);
return true;
}

こんな感じに書ける


public class Hoge : BindableBase
{
private readonly TextView _textView;

public Hoge()
{
_textView = new TextView();
}

public string Text
{
get => _textView.Text;
set => SetProperty(_textView, tv => tv.Text, value);
}
}

ちょっと幸せになれるかもしれない。