Skip to content

Commit

Permalink
Merge pull request #204 from runceel/main
Browse files Browse the repository at this point in the history
v7.5.0
  • Loading branch information
runceel authored Oct 17, 2020
2 parents c0912fc + de5331e commit 2ff1ac5
Show file tree
Hide file tree
Showing 24 changed files with 1,121 additions and 83 deletions.
40 changes: 40 additions & 0 deletions Benchmark/Benchmark.V6/BasicUsages.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using BenchmarkDotNet.Attributes;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;

namespace ReactivePropertyBenchmark
{
Expand Down Expand Up @@ -33,5 +36,42 @@ public void BasicForReactivePropertySlim()
rp.Value = "xxxx";
rp.Dispose();
}

[Benchmark]
public IObservable<string> ObserveProperty()
{
var p = new Person();
return p.ObserveProperty(x => x.Name);
}

[Benchmark]
public ReactiveProperty<string> ToReactivePropertyAsSynchronized()
{
var p = new Person();
return p.ToReactivePropertyAsSynchronized(x => x.Name);
}
}

class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

private void SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return;
}

field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
}
}
145 changes: 137 additions & 8 deletions Source/ReactiveProperty.Core/Internals/AccessorCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal static class AccessorCache<TType>
/// <returns></returns>
public static Func<TType, TProperty> LookupGet<TProperty>(Expression<Func<TType, TProperty>> propertySelector, out string propertyName)
{
propertyName = GetPropertyName(propertySelector);
propertyName = ExpressionTreeUtils.GetPropertyName(propertySelector);
Delegate accessor;

lock (s_getCache)
Expand All @@ -38,16 +38,28 @@ public static Func<TType, TProperty> LookupGet<TProperty>(Expression<Func<TType,
return (Func<TType, TProperty>)accessor;
}

private static string GetPropertyName<TProperty>(Expression<Func<TType, TProperty>> propertySelector)
/// <summary>
/// Lookups the get.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="propertySelector">The property selector.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
public static Func<TType, TProperty> LookupNestedGet<TProperty>(Expression<Func<TType, TProperty>> propertySelector, out string propertyName)
{
if (!(propertySelector.Body is MemberExpression memberExpression))
propertyName = ExpressionTreeUtils.GetPropertyPath(propertySelector);
Delegate accessor;

lock (s_getCache)
{
if (!(propertySelector.Body is UnaryExpression unaryExpression)) { throw new ArgumentException(nameof(propertySelector)); }
memberExpression = unaryExpression.Operand as MemberExpression;
if (memberExpression == null) { throw new ArgumentException(nameof(propertySelector)); }
if (!s_getCache.TryGetValue(propertyName, out accessor))
{
accessor = propertySelector.Compile();
s_getCache.Add(propertyName, accessor);
}
}

return memberExpression.Member.Name;
return (Func<TType, TProperty>)accessor;
}

/// <summary>
Expand All @@ -59,7 +71,7 @@ private static string GetPropertyName<TProperty>(Expression<Func<TType, TPropert
/// <returns></returns>
public static Action<TType, TProperty> LookupSet<TProperty>(Expression<Func<TType, TProperty>> propertySelector, out string propertyName)
{
propertyName = GetPropertyName(propertySelector);
propertyName = ExpressionTreeUtils.GetPropertyName(propertySelector);
Delegate accessor;

lock (s_setCache)
Expand All @@ -84,4 +96,121 @@ private static Delegate CreateSetAccessor<TProperty>(Expression<Func<TType, TPro
return lambda.Compile();
}
}

internal static class AccessorCache
{
private static readonly Dictionary<Type, Type> _accessorCacheTypeCache = new Dictionary<Type, Type>();
private static readonly Dictionary<Type, Dictionary<string, Delegate>> _getCache = new Dictionary<Type, Dictionary<string, Delegate>>();
private static readonly Dictionary<Type, Dictionary<string, Delegate>> _setCache = new Dictionary<Type, Dictionary<string, Delegate>>();

private static Dictionary<string, Delegate> GetGetCacheByType(Type type)
{
lock (_getCache)
{
if (_getCache.TryGetValue(type, out var cache))
{
return cache;
}

var accessorType = GetAccessorCacheTypeByType(type);
cache = (Dictionary<string, Delegate>)accessorType.GetField("s_getCache", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
_getCache.Add(type, cache);
return cache;
}
}

private static Dictionary<string, Delegate> GetSetCacheByType(Type type)
{
lock (_setCache)
{
if (_setCache.TryGetValue(type, out var cache))
{
return cache;
}

var accessorType = GetAccessorCacheTypeByType(type);
cache = (Dictionary<string, Delegate>)accessorType.GetField("s_setCache", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
_setCache.Add(type, cache);
return cache;
}
}

private static Type GetAccessorCacheTypeByType(Type type)
{
lock (_accessorCacheTypeCache)
{
if (_accessorCacheTypeCache.TryGetValue(type, out var result))
{
return result;
}

result = typeof(AccessorCache<>).MakeGenericType(type);
_accessorCacheTypeCache.Add(type, result);
return result;
}
}

public static Delegate LookupGet(Type type, string propertyName)
{
var getCache = GetGetCacheByType(type);
lock (getCache)
{
if (getCache.TryGetValue(propertyName, out var accessor))
{
return accessor;
}

return CreateAndCacheGetAccessor(type, propertyName, getCache);
}
}

public static Delegate LookupSet(Type type, string propertyName)
{
var setCache = GetSetCacheByType(type);
lock (setCache)
{
if (setCache.TryGetValue(propertyName, out var accessor))
{
return accessor;
}

return CreateAndCacheSetAccessor(type, propertyName, setCache);
}
}



private static Delegate CreateAndCacheGetAccessor(Type type, string propertyName, Dictionary<string, Delegate> cache)
{
var propertyInfo = type.GetProperty(propertyName);
var accessor = CreateGetAccessor(type, propertyInfo);
cache.Add(propertyName, accessor);
return accessor;
}

private static Delegate CreateAndCacheSetAccessor(Type type, string propertyName, Dictionary<string, Delegate> cache)
{
var propertyInfo = type.GetProperty(propertyName);
var accessor = CreateSetAccessor(type, propertyInfo);
cache.Add(propertyName, accessor);
return accessor;
}

private static Delegate CreateSetAccessor(Type type, PropertyInfo propertyInfo)
{
var selfParameter = Expression.Parameter(type, "self");
var valueParameter = Expression.Parameter(propertyInfo.PropertyType, "value");
var body = Expression.Assign(Expression.Property(selfParameter, propertyInfo), valueParameter);
var lambda = Expression.Lambda(typeof(Action<,>).MakeGenericType(type, propertyInfo.PropertyType), body, selfParameter, valueParameter);
return lambda.Compile();
}

private static Delegate CreateGetAccessor(Type type, PropertyInfo propertyInfo)
{
var selfParameter = Expression.Parameter(type, "self");
var body = Expression.Property(selfParameter, propertyInfo);
var lambda = Expression.Lambda(typeof(Func<,>).MakeGenericType(type, propertyInfo.PropertyType), body, selfParameter);
return lambda.Compile();
}
}
}
61 changes: 61 additions & 0 deletions Source/ReactiveProperty.Core/Internals/ExpressionTreeUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;

namespace Reactive.Bindings.Internals
{
internal static class ExpressionTreeUtils
{
public static string GetPropertyPath<TType, TProperty>(Expression<Func<TType, TProperty>> propertySelector)
{
if (!(propertySelector.Body is MemberExpression memberExpression))
{
if (!(propertySelector.Body is UnaryExpression unaryExpression)) { throw new ArgumentException(nameof(propertySelector)); }
memberExpression = unaryExpression.Operand as MemberExpression;
if (memberExpression == null) { throw new ArgumentException(nameof(propertySelector)); }
}

var tokens = new LinkedList<string>();
while (memberExpression != null)
{
tokens.AddFirst(memberExpression.Member.Name);
memberExpression = memberExpression.Expression as MemberExpression;
}

return string.Join(".", tokens);
}


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

return memberExpression.Member.Name;
}

public static bool IsNestedPropertyPath<TSubject, TProperty>(Expression<Func<TSubject, TProperty>> propertySelector)
{
if (propertySelector.Body is MemberExpression member)
{
return !(member.Expression is ParameterExpression);
};

if (propertySelector.Body is UnaryExpression unary)
{
if (unary.Operand is MemberExpression unaryMember)
{
return !(unaryMember.Expression is ParameterExpression);
}
}

throw new ArgumentException();
}

}
}
4 changes: 2 additions & 2 deletions Source/ReactiveProperty.Core/Internals/InternalVisibleTo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("ReactiveProperty, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a17d9f6e0d29671569d60841092ca470fd3105e6df77bbf6be713ace63c959e0d199dc7c16310a698bffb5133a6ffce1c87646f1f2b20e79b6c51a4dc4a44c8efcc5fbf0063353254d1cf9c094fbeb3133594b23c62c4ce67bcf1e445ef0b3770a6c885b79a3a2979c63e93e699f780c1d742fe70bacb6a0b2464498d37f83f7")]
[assembly:InternalsVisibleTo("ReactiveProperty.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f56d09c52b0e027bc83bba3577eec7d30f15c7cd797d94f727d235ced2d26d95ab0e2a8d7e322b0c4eaf4f04838cbcaa25d8d7ec5c4a671160c9e112f5ed158c145a392ad205c3ef7c5f6bbea642930e65e41e00fd3d9404fe37a956217d65fe4824df826d3bb58fad44a185068242d346546743685e94f52801d3f5cbd707d6")]
[assembly: InternalsVisibleTo("ReactiveProperty, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a17d9f6e0d29671569d60841092ca470fd3105e6df77bbf6be713ace63c959e0d199dc7c16310a698bffb5133a6ffce1c87646f1f2b20e79b6c51a4dc4a44c8efcc5fbf0063353254d1cf9c094fbeb3133594b23c62c4ce67bcf1e445ef0b3770a6c885b79a3a2979c63e93e699f780c1d742fe70bacb6a0b2464498d37f83f7")]
[assembly: InternalsVisibleTo("ReactiveProperty.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f56d09c52b0e027bc83bba3577eec7d30f15c7cd797d94f727d235ced2d26d95ab0e2a8d7e322b0c4eaf4f04838cbcaa25d8d7ec5c4a671160c9e112f5ed158c145a392ad205c3ef7c5f6bbea642930e65e41e00fd3d9404fe37a956217d65fe4824df826d3bb58fad44a185068242d346546743685e94f52801d3f5cbd707d6")]
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ public static class DisposePreviousValueExtensions
/// <summary>
/// Dispose previous value automatically.
/// </summary>
public static IObservable<T> DisposePreviousValue<T>(this IObservable<T> source) =>
public static IObservable<T> DisposePreviousValue<T>(this IObservable<T> source) =>
Observable.Create<T>(ox =>
{
var d = new SerialDisposable();
return new CompositeDisposable(
source.Do(x => d.Disposable = x as IDisposable).Do(ox).Subscribe(),
source.Do(x => d.Disposable = x as IDisposable).Do(ox).Subscribe(),
d);
});
}
Expand Down
Loading

0 comments on commit 2ff1ac5

Please sign in to comment.