Created
May 12, 2014 17:11
-
-
Save petarvucetin/65e860bc720b6ee11524 to your computer and use it in GitHub Desktop.
Using dynamic to simplify reflection via David Ebb
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.Dynamic; | |
using System.Linq; | |
using System.Reflection; | |
namespace Core.Common | |
{ | |
//FROM http://blogs.msdn.com/b/davidebb/archive/2010/01/18/use-c-4-0-dynamic-to-drastically-simplify-your-private-reflection-code.aspx | |
//doesnt count to line counts :) | |
public class PrivateReflectionDynamicObject : DynamicObject | |
{ | |
private static readonly IDictionary<Type, IDictionary<string, IProperty>> _propertiesOnType = new ConcurrentDictionary<Type, IDictionary<string, IProperty>>(); | |
// Simple abstraction to make field and property access consistent | |
interface IProperty | |
{ | |
string Name { get; } | |
object GetValue(object obj, object[] index); | |
void SetValue(object obj, object val, object[] index); | |
} | |
// IProperty implementation over a PropertyInfo | |
class Property : IProperty | |
{ | |
internal PropertyInfo PropertyInfo { get; set; } | |
string IProperty.Name | |
{ | |
get | |
{ | |
return PropertyInfo.Name; | |
} | |
} | |
object IProperty.GetValue(object obj, object[] index) | |
{ | |
return PropertyInfo.GetValue(obj, index); | |
} | |
void IProperty.SetValue(object obj, object val, object[] index) | |
{ | |
PropertyInfo.SetValue(obj, val, index); | |
} | |
} | |
// IProperty implementation over a FieldInfo | |
class Field : IProperty | |
{ | |
internal FieldInfo FieldInfo { get; set; } | |
string IProperty.Name | |
{ | |
get | |
{ | |
return FieldInfo.Name; | |
} | |
} | |
object IProperty.GetValue(object obj, object[] index) | |
{ | |
return FieldInfo.GetValue(obj); | |
} | |
void IProperty.SetValue(object obj, object val, object[] index) | |
{ | |
FieldInfo.SetValue(obj, val); | |
} | |
} | |
private object RealObject { get; set; } | |
private const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; | |
internal static object WrapObjectIfNeeded(object o) | |
{ | |
// Don't wrap primitive types, which don't have many interesting internal APIs | |
if (o == null || o.GetType().IsPrimitive || o is string) | |
return o; | |
return new PrivateReflectionDynamicObject() { RealObject = o }; | |
} | |
public override bool TryGetMember(GetMemberBinder binder, out object result) | |
{ | |
IProperty prop = GetProperty(binder.Name); | |
// Get the property value | |
result = prop.GetValue(RealObject, index: null); | |
// Wrap the sub object if necessary. This allows nested anonymous objects to work. | |
result = WrapObjectIfNeeded(result); | |
return true; | |
} | |
public override bool TrySetMember(SetMemberBinder binder, object value) | |
{ | |
IProperty prop = GetProperty(binder.Name); | |
// Set the property value | |
prop.SetValue(RealObject, value, index: null); | |
return true; | |
} | |
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) | |
{ | |
// The indexed property is always named "Item" in C# | |
IProperty prop = GetIndexProperty(); | |
result = prop.GetValue(RealObject, indexes); | |
// Wrap the sub object if necessary. This allows nested anonymous objects to work. | |
result = WrapObjectIfNeeded(result); | |
return true; | |
} | |
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) | |
{ | |
// The indexed property is always named "Item" in C# | |
IProperty prop = GetIndexProperty(); | |
prop.SetValue(RealObject, value, indexes); | |
return true; | |
} | |
// Called when a method is called | |
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) | |
{ | |
result = InvokeMemberOnType(RealObject.GetType(), RealObject, binder.Name, args); | |
// Wrap the sub object if necessary. This allows nested anonymous objects to work. | |
result = WrapObjectIfNeeded(result); | |
return true; | |
} | |
public override bool TryConvert(ConvertBinder binder, out object result) | |
{ | |
result = Convert.ChangeType(RealObject, binder.Type); | |
return true; | |
} | |
public override string ToString() | |
{ | |
return RealObject.ToString(); | |
} | |
private IProperty GetIndexProperty() | |
{ | |
// The index property is always named "Item" in C# | |
return GetProperty("Item"); | |
} | |
private IProperty GetProperty(string propertyName) | |
{ | |
// Get the list of properties and fields for this type | |
IDictionary<string, IProperty> typeProperties = GetTypeProperties(RealObject.GetType()); | |
// Look for the one we want | |
IProperty property; | |
if (typeProperties.TryGetValue(propertyName, out property)) | |
{ | |
return property; | |
} | |
// The property doesn't exist | |
// Get a list of supported properties and fields and show them as part of the exception message | |
// For fields, skip the auto property backing fields (which name start with <) | |
var propNames = typeProperties.Keys.Where(name => name[0] != '<').OrderBy(name => name); | |
throw new ArgumentException( | |
String.Format( | |
"The property {0} doesn't exist on type {1}. Supported properties are: {2}", | |
propertyName, RealObject.GetType(), String.Join(", ", propNames))); | |
} | |
private static IDictionary<string, IProperty> GetTypeProperties(Type type) | |
{ | |
// First, check if we already have it cached | |
IDictionary<string, IProperty> typeProperties; | |
if (_propertiesOnType.TryGetValue(type, out typeProperties)) | |
{ | |
return typeProperties; | |
} | |
// Not cache, so we need to build it | |
typeProperties = new ConcurrentDictionary<string, IProperty>(); | |
// First, add all the properties | |
foreach (PropertyInfo prop in type.GetProperties(bindingFlags).Where(p => p.DeclaringType == type)) | |
{ | |
typeProperties[prop.Name] = new Property() { PropertyInfo = prop }; | |
} | |
// Now, add all the fields | |
foreach (FieldInfo field in type.GetFields(bindingFlags).Where(p => p.DeclaringType == type)) | |
{ | |
typeProperties[field.Name] = new Field() { FieldInfo = field }; | |
} | |
// Finally, recurse on the base class to add its fields | |
if (type.BaseType != null) | |
{ | |
foreach (IProperty prop in GetTypeProperties(type.BaseType).Values) | |
{ | |
typeProperties[prop.Name] = prop; | |
} | |
} | |
// Cache it for next time | |
_propertiesOnType[type] = typeProperties; | |
return typeProperties; | |
} | |
private static object InvokeMemberOnType(Type type, object target, string name, object[] args) | |
{ | |
try | |
{ | |
// Try to incoke the method | |
return type.InvokeMember( | |
name, | |
BindingFlags.InvokeMethod | bindingFlags, | |
null, | |
target, | |
args); | |
} | |
catch (MissingMethodException) | |
{ | |
// If we couldn't find the method, try on the base class | |
if (type.BaseType != null) | |
{ | |
return InvokeMemberOnType(type.BaseType, target, name, args); | |
} | |
//quick greg hack to allow methods to not exist! | |
return null; | |
} | |
} | |
} | |
public static class PrivateReflectionDynamicObjectExtensions | |
{ | |
public static dynamic AsDynamic(this object o) | |
{ | |
return PrivateReflectionDynamicObject.WrapObjectIfNeeded(o); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment