Skip to content

Instantly share code, notes, and snippets.

@sdcondon
Last active October 12, 2019 09:09
Show Gist options
  • Save sdcondon/dbbd40903084239221c0c4df2ab2ab7d to your computer and use it in GitHub Desktop.
Save sdcondon/dbbd40903084239221c0c4df2ab2ab7d to your computer and use it in GitHub Desktop.
Uses LINQ expressions to decompose and recompose objects, storing each property in a different backing store.
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq.Expressions;
/// <summary>
/// Decomposes <see paramref="TValue"/> objects and stores their properties in separate backing stores.
/// Useful if individual properties can be stored more efficiently (e.g. spatial indexing).
/// </summary>
/// <typeparam name="TKey">The type of the key by which objects will be stored.</typeparam>
/// <typeparam name="TValue">The type of objects to be stored.</typeparam>
public sealed class LinqDecomposer<TKey, TValue> where TValue : new()
{
private readonly Func<TKey, TValue> getter;
private readonly Action<TKey, TValue> setter;
/// <summary>
/// Initializes a new instance of the <see cref="LinqDecomposer{TKey, TValue}"/> class.
/// </summary>
/// <param name="makeStore">Delegate to create store from property info. Returned object must have an appropriate indexer (that is, by TKey and returning the property type).</param>
public LinqDecomposer(Func<PropertyInfo, object> makeStore)
{
var indexParam = Expression.Parameter(typeof(TKey));
var valueParam = Expression.Parameter(typeof(TValue));
var getterMemberAssignments = new List<MemberAssignment>();
var setterStoreAssignments = new List<Expression>();
foreach (var prop in typeof(TValue).GetRuntimeProperties())
{
var store = makeStore(prop);
var storeIndexer = store.GetType().GetTypeInfo().GetDeclaredProperty("Item");
var getterStoreIndex = Expression.MakeIndex(
Expression.Constant(store),
storeIndexer,
new[] { indexParam });
getterMemberAssignments.Add(Expression.Bind(prop, getterStoreIndex));
var setterStoreIndex = Expression.MakeIndex(
Expression.Constant(store),
storeIndexer,
new[] { indexParam });
var setterPropValue = Expression.Property(valueParam, prop.GetMethod);
setterStoreAssignments.Add(Expression.Assign(setterStoreIndex, setterPropValue));
}
var getterBody = Expression.MemberInit(Expression.New(typeof(TValue)), getterMemberAssignments);
this.getter = Expression.Lambda<Func<TKey, TValue>>(getterBody, indexParam).Compile();
var setterBody = Expression.Block(setterStoreAssignments);
this.setter = Expression.Lambda<Action<TKey, TValue>>(setterBody, indexParam, valueParam).Compile();
}
public TValue this[TKey key]
{
get => this.getter(key);
set => this.setter(key, value);
}
}
public class Foo
{
public string String { get; set; }
public int Int { get; set; }
}
public static class Program
{
public static void Main()
{
var x = new LinqDecomposer<string, Foo>(p =>
{
// Dictionary<,> admittedly not a particularly useful example..
return Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(typeof(string), p.PropertyType));
});
x["a"] = new Foo() { String = "", Int = 1 };
x["b"] = new Foo() { String = "a", Int = 0 };
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment