Making a DynamicObject implementation be case insensitive
To me this seems to be a very straight forward request but I cannot find the ‘easy’ way to do this in .Net even though there seems to be a clear indication that this is supported Out of The Box. Here’s an example of what I want to do:
//Simple class public class Foo { public string Hello {get;set;} public string World {get;set;} public int AddNumbers(int num1, int num2) { return num1 + num2; } } //Use the simple class but allow case insensitive // access to it's properties and members var foo = new Foo{ Hello = "hi", World = "earth" }; //make it dynamic dynamic dFoo = foo; //access the dynamic object's props and methods // in a case insensitive way: Assert.AreEqual(foo.Hello, dFoo.hello); Assert.AreEqual(foo.World, dFoo.world); Assert.AreEqual(foo.AddNumbers(1,2), dFoo.addNumbers(1,2));
DynamicObject
In order to do the above, you can implement ‘DynamicObject’ and override a couple of methods:
public class Foo : DynamicObject { public string Hello {get;set;} public string World {get;set;} public int AddNumbers(int num1, int num2) { return num1 + num2; } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { //Here we would need to match lower cased binder.Name // to a real method that our object has var name = binder.Name.ToLowerInvariant(); //TODO: Do the matching return base.TryInvokeMember(binder, args, out result); } public override bool TryGetMember(GetMemberBinder binder, out object result) { //Here we would need to match lower cased binder.Name // to a real property that our object has var name = binder.Name.ToLowerInvariant(); //TODO: Do the matching return base.TryGetMember(binder, out result); } }
I don’t know about you, but I’d rather not have to write all the code to do the matching and executing. This seems like a pretty straight forward thing to want to do and after looking at the innards of some of this stuff, it turns out that the ‘GetMemberBinder’ object has a property conveniently called `IgnoreCase`… surely this functionality already exists!?
Well I unfortunately cannot find anything worthwhile on Google about ‘GetMemberBinder.IgnoreCase’, I also can’t really see where or how it gets used when looking at decompiled sources.
So I’ve reverted to handling this on my own but I would really really really like to know if there’s a built in and simple way to do this in .Net since it sure seems like it should exist.
CaseInsensitiveDynamicObject
This is a base class implementation to achieve what I want. It’s not error proof and I’m sure with some objects it won’t work 100% as expected but for simple objects this will work. Using this implementation is super easy:
public class Foo : CaseInsensitiveDynamicObject { public string Hello {get;set;} public string World {get;set;} public int AddNumbers(int num1, int num2) { return num1 + num2; } }
By doing that, my original requirement works! Here’s the code that does this … because I was lazy and didn’t want to create more classes than I needed I’m using Tuples so the code is fully of super generic fun ;)
/// <summary> /// This will check enable dynamic access to properties and methods in a case insensitive manner /// </summary> /// <typeparam name="T"></typeparam> /// <remarks> /// This works by using reflection on the type - the reflection lookup is lazy so it will not execute unless a dynamic method needs to be accessed /// </remarks> public abstract class CaseInsensitiveDynamicObject<T> : DynamicObject where T: class { /// <summary> /// Used for dynamic access for case insensitive property access /// </summary>` private static readonly Lazy<IDictionary<string, Func<T, object>>> CaseInsensitivePropertyAccess = new Lazy<IDictionary<string, Func<T, object>>>(() => { var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public) .DistinctBy(x => x.Name); return props.Select(propInfo => { var name = propInfo.Name.ToLowerInvariant(); Func<T, object> getVal = propInfo.GetValue; return new KeyValuePair<string, Func<T, object>>(name, getVal); }).ToDictionary(x => x.Key, x => x.Value); }); /// <summary> /// Used for dynamic access for case insensitive property access /// </summary> private static readonly Lazy<IDictionary<string, Tuple<ParameterInfo[], Func<T, object[], object>>>> CaseInsensitiveMethodAccess = new Lazy<IDictionary<string, Tuple<ParameterInfo[], Func<T, object[], object>>>>(() => { var props = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public) .Where(x => x.IsSpecialName == false && x.IsVirtual == false) .DistinctBy(x => x.Name); return props.Select(methodInfo => { var name = methodInfo.Name.ToLowerInvariant(); Func<T, object[], object> getVal = methodInfo.Invoke; var val = new Tuple<ParameterInfo[], Func<T, object[], object>>(methodInfo.GetParameters(), getVal); return new KeyValuePair<string, Tuple<ParameterInfo[], Func<T, object[], object>>>(name, val); }).ToDictionary(x => x.Key, x => x.Value); }); /// <summary> /// Used for dynamic access for case insensitive method access /// </summary> public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { var name = binder.Name.ToLowerInvariant(); if (CaseInsensitiveMethodAccess.Value.ContainsKey(name) == false) return base.TryInvokeMember(binder, args, out result); var val = CaseInsensitiveMethodAccess.Value[name]; var parameters = val.Item1; var callback = val.Item2; var fullArgs = new List<object>(args); if (args.Length <= parameters.Length) { //need to fill them up if they're optional for (var i = args.Length; i < parameters.Length; i++) { if (parameters[i].IsOptional) { fullArgs.Add(parameters[i].DefaultValue); } } if (fullArgs.Count == parameters.Length) { result = callback((T)(object)this, fullArgs.ToArray()); return true; } } return base.TryInvokeMember(binder, args, out result); } public override bool TryGetMember(GetMemberBinder binder, out object result) { var name = binder.Name.ToLowerInvariant(); if (CaseInsensitivePropertyAccess.Value.ContainsKey(name) == false) return base.TryGetMember(binder, out result); result = CaseInsensitivePropertyAccess.Value[name]((T)(object)this); return true; } }
If anyone know how to make this work OOTB with .Net, perhaps using the `GetMemberBinder.IgnoreCase` I’d love to hear it!