Last active
August 4, 2017 16:11
-
-
Save JohanLarsson/43739a199b67c18cac0b93951e253be2 to your computer and use it in GitHub Desktop.
This file contains hidden or 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.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"); | |
| } | |
| } |
This file contains hidden or 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 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