Skip to content

Instantly share code, notes, and snippets.

@JohanLarsson
Last active August 4, 2017 16:11
Show Gist options
  • Save JohanLarsson/43739a199b67c18cac0b93951e253be2 to your computer and use it in GitHub Desktop.
Save JohanLarsson/43739a199b67c18cac0b93951e253be2 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
public delegate TResult PropertyPath<in T, out TResult>(T arg);
public static class PropertyPathExt
{
private static readonly ConcurrentDictionary<Delegate, IReadOnlyList<PropertyInfo>> Cache = new ConcurrentDictionary<Delegate, IReadOnlyList<PropertyInfo>>();
private static readonly OpCode[] Codes =
{
OpCodes.Ldarg_1,
OpCodes.Callvirt,
OpCodes.Ret
};
private static readonly Type[] GenericTypeArguments = new Type[0];
public static IReadOnlyList<PropertyInfo> Properties<TSource, TValue>(this PropertyPath<TSource, TValue> path)
{
return Cache.GetOrAdd(path, x => GetProperties((PropertyPath<TSource, TValue>)x));
}
private static IReadOnlyList<PropertyInfo> GetProperties<TSource, TValue>(PropertyPath<TSource, TValue> path)
{
var il = path.Method.GetMethodBody().GetILAsByteArray();
var offset = 0;
var opCode = GetOpCode(il, ref offset);
if (opCode != OpCodes.Ldarg_1)
{
throw new InvalidOperationException();
}
var properties = new List<PropertyInfo>();
while (offset < il.Length)
{
opCode = GetOpCode(il, ref offset);
if (opCode == OpCodes.Ret)
{
if (offset != il.Length)
{
throw new InvalidOperationException();
}
return properties;
}
else if (opCode == OpCodes.Callvirt)
{
var metaDataToken = BitConverter.ToInt32(il, offset);
var method =
path.Method.Module.ResolveMethod(
metaDataToken,
GenericTypeArguments,
null);
properties.Add(method.DeclaringType.GetProperty(method.Name.Substring(4)));
offset += 4;
}
else
{
throw new ArgumentOutOfRangeException(nameof(opCode), opCode, "Illegal op code");
}
}
throw new InvalidOperationException();
}
private static OpCode GetOpCode(byte[] il, ref int offset)
{
var code = (short)il[offset++];
if (code == 0xfe)
{
code = (short)(il[offset++] | 0xfe00);
}
foreach (var opCode in Codes)
{
if (code == opCode.Value)
{
return opCode;
}
}
throw new ArgumentOutOfRangeException(nameof(code), code, "not supported code");
}
}
using NUnit.Framework;
public class Meh
{
public string Text { get; set; }
[Test]
public void Func()
{
var expected = new[]
{
this.GetType().GetProperty(nameof(this.Text)),
typeof(string).GetProperty("Length")
};
PropertyPath<Meh, int> propertyPath = x => x.Text.Length;
CollectionAssert.AreEqual(expected, propertyPath.Properties());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment