Trying to figure out how to efficiently cache property calculations with dependency tracking to invalidate the cache. Here is the syntax I have at the moment (one Cache
instance supports multiple object properties):
class Multiplication
{
public int Arg1 { get; set; }
public int Arg2 { get; set; }
public int Result => Cache
.Lazy(() => Arg1 * Arg2)
.From(() => Arg1, () => Arg2);
Cache Cache { get; } = new Cache();
}
Here Result
is recalculated on Arg*
change. Implementation classes:
public class Cache
{
public LazyProperty<T> Lazy<T>(
Func<T> factory,
[CallerMemberName] string memberName = "") =>
new LazyProperty<T>(
value => (Cached<T>)Store.GetOrAdd(memberName, value),
new Cached<T>(factory));
ConcurrentDictionary<string, object> Store { get; } =
new ConcurrentDictionary<string, object>();
}
This one helps with flow API:
public class LazyProperty<T>
{
public static implicit operator T(LazyProperty<T> property) => property.Resolve();
internal LazyProperty(Func<Cached<T>, Cached<T>> getOrAdd, Cached<T> value)
{
GetOrAdd = getOrAdd;
Value = value;
}
public LazyProperty<T> From(params Func<object>[] dependencies) =>
new LazyProperty<T>(GetOrAdd, Value.Depending(dependencies));
Func<Cached<T>, Cached<T>> GetOrAdd { get; }
Cached<T> Value { get; }
Cached<T> Resolve() => GetOrAdd(Value);
}
Dependency tracking done by:
public class Cached<T>
{
public static implicit operator T(Cached<T> cache) => cache.Value;
public Cached(Func<T> factory, params Func<object>[] dependencies)
{
if (factory == null)
throw new ArgumentNullException("factory");
if(dependencies == null || dependencies.Any(d => d == null))
throw new ArgumentNullException("dependancies");
_factory = factory;
_dependencies = dependencies;
}
public Cached<T> Depending(params Func<object>[] dependencies) =>
new Cached<T>(_factory, _dependencies.Concat(dependencies).ToArray());
readonly Func<T> _factory;
readonly Func<object>[] _dependencies;
object[] _context = null;
T _value = default(T);
T Value
{
get
{
var context = _dependencies
.Select(d => d())
.ToArray();
if (_context?.SequenceEqual(context) == true)
return _value;
lock (this)
{
if (_context?.SequenceEqual(context) == true)
return _value;
_context = context;
_value = _factory();
return _value;
}
}
}
}
Any ideas how to simplify/speed it up? Thanks.