Skip to content

Instantly share code, notes, and snippets.

@vchirikov
Created May 21, 2019 11:55
Show Gist options
  • Save vchirikov/c7ddb47dd7160aec794908d02d5f2596 to your computer and use it in GitHub Desktop.
Save vchirikov/c7ddb47dd7160aec794908d02d5f2596 to your computer and use it in GitHub Desktop.
Factory for creating readers of the properties of the anonymous (and not only) types
void Main()
{
var generator = new ObjectPropertiesReadersFactory();
var obj = new { @class = "css-class", @value = 2, items = new[] {1,2,3} };
// var obj = new TestClass{ items = "123" };
var reader = generator.GetReader(obj.GetType());
var result = new Dictionary<string, object>();
reader(result, obj);
result.Dump();
}
public class TestClass{
public string items {get;set;}
}
public class ObjectPropertiesReadersFactory
{
/// <summary>
/// Cache of type readers (lazy for thread-safe delegate creating)
/// </summary>
private static readonly ConcurrentDictionary<Type, Lazy<Action<IDictionary<string, object>, object>>> _objectReadersCache = new ConcurrentDictionary<Type, Lazy<Action<IDictionary<string, object>, object>>>();
private static readonly Type[] _readerArgs = new []{ typeof(IDictionary<string, object>), typeof(object)};
private static readonly MethodInfo _dictAddMethodInfo = typeof(IDictionary<string, object>).GetMethod("Add");
public Action<IDictionary<string, object>, object> GetReader(Type type)
=> _objectReadersCache.GetOrAdd(type, t => new Lazy<Action<IDictionary<string, object>, object>>(() => CreateReaderDelegate(t))).Value;
private static Action<IDictionary<string, object>, object> CreateReaderDelegate(Type type)
{
var dm = new DynamicMethod($"TypeReader_{Guid.NewGuid():N}", null, _readerArgs, restrictedSkipVisibility: true);
var il = dm.GetILGenerator();
/* Example of generated IL:
ldarg.1
castclass AnonType
stloc.0
ldarg.0
ldstr "ClassExample"
ldloc.0
callvirt instance string AnonType::get_ClassExample()
callvirt instance void class [mscorlib]System.Collections.Generic.IDictionary`2<string, object>::Add(!0, !1)
ldarg.0
ldstr "ValueExample"
ldloc.0
callvirt instance int32 AnonType::get_ValueExample()
box [mscorlib]System.Int32
callvirt instance void class [mscorlib]System.Collections.Generic.IDictionary`2<string, object>::Add(!0, !1)
ret
*/
il.DeclareLocal(type);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Castclass, type);
il.Emit(OpCodes.Stloc_0);
// Indexed properties are not useful for grabbing
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(prop => prop.GetIndexParameters().Length == 0 && prop.GetMethod != null);
foreach (var p in properties)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, p.Name);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Callvirt, p.GetGetMethod());
if(p.PropertyType.IsValueType)
il.Emit(OpCodes.Box, p.PropertyType);
il.Emit(OpCodes.Callvirt, _dictAddMethodInfo);
}
il.Emit(OpCodes.Ret);
return (Action<IDictionary<string, object>, object>) dm.CreateDelegate(typeof(Action<IDictionary<string, object>, object>));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment