Last active
October 12, 2019 09:09
-
-
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.
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.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