6
\$\begingroup\$

I've been playing about with runtime compilation to allow me to get/set runtime property values in C#, and so far I have come up with a class.

This allows me to write code like the following which works well and is much faster than standard reflection:

TypeWrapper wrapper = new TypeWrapper(target);
wrapper.Set("Value", "abc");
return (string)wrapper.Get("Value");

What I'm wondering is whether there is a way to make this even faster. I could go into metaprogramming and use Reflection.Emit, but that adds a vastly increased order of complexity for other developers to follow.

Is there something obvious you can see?

Here's the current benchmarks using Benchmark.NET:

Total time: 00:01:47 (107.64 sec)
// * Summary *
BenchmarkDotNet=v0.9.4.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Xeon(R) CPU E5-1650 0 @ 3.20GHz, ProcessorCount=12
Frequency=3117484 ticks, Resolution=320.7715 ns, Timer=TSC
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE
JitModules=clrjit-v4.6.1063.1
 Method | Median | StdDev | Scaled |
--------------------------- |------------ |----------- |------- |
 1. Static C# | 2.5401 ns | 0.6457 ns | 1.00 |
 2. Dynamic C# | 35.1664 ns | 0.4643 ns | 13.84 |
 3. PropertyInfo | 430.8785 ns | 6.4399 ns | 169.63 |
 4. PropertyDescriptor | 920.3483 ns | 59.1531 ns | 362.32 |
 5. TypeWrapper | 117.0505 ns | 1.6790 ns | 46.08 |
// ***** BenchmarkRunner: End *****
1. Static C#: 2.54 ns
2. Dynamic C#: 35.17 ns
3. PropertyInfo: 430.88 ns
4. PropertyDescriptor: 920.35 ns
5. TypeWrapper: 117.05 ns

And the class itself:

public class TypeWrapper
{
 private readonly dynamic dyn;
 private readonly Dictionary<string, CallSite<Action<CallSite, object, object>>> setters
 = new Dictionary<string, CallSite<Action<CallSite, object, object>>>();
 private readonly Dictionary<string, CallSite<Func<CallSite, object, object>>> getters
 = new Dictionary<string, CallSite<Func<CallSite, object, object>>>();
 public TypeWrapper(object d)
 {
 this.dyn = d;
 Type type = d.GetType();
 foreach (var p in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
 {
 string name = p.Name;
 CallSite<Action<CallSite, object, object>> set = CallSite<Action<CallSite, object, object>>.Create(
 Microsoft.CSharp.RuntimeBinder.Binder.SetMember(
 CSharpBinderFlags.None,
 name,
 type,
 new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) ,
 CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
 this.setters.Add(name, set);
 CallSite<Func<CallSite, object, object>> get = CallSite<Func<CallSite, object, object>>.Create(
 Microsoft.CSharp.RuntimeBinder.Binder.GetMember(
 CSharpBinderFlags.None,
 name,
 type,
 new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
 this.getters.Add(name, get);
 }
 }
 public void Set(string name, object value)
 {
 var set = this.setters[name];
 set.Target(set, this.dyn, value);
 }
 public object Get(string name)
 {
 var get = this.getters[name];
 return get.Target(get, this.dyn);
 }
}
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Apr 27, 2016 at 6:28
\$\endgroup\$
0

1 Answer 1

10
\$\begingroup\$

First, a trivial optimization: there is no reason for dyn to be dynamic: all it does is to make the Target invocation dynamic, which here only makes it slower.

On my computer, this makes Get/Set about 25 % faster.


Another possibility is to use Expression instead of CallSite. Since Expression is not as dynamic as dynamic, it can compile to simpler code. For me, using this results in 50 % better speed than your code after applying the previous optimization.

The code:

public class TypeWrapper
{
 private readonly object dyn;
 private readonly Dictionary<string, Action<object, object>> setters
 = new Dictionary<string, Action<object, object>>();
 private readonly Dictionary<string, Func<object, object>> getters
 = new Dictionary<string, Func<object, object>>();
 public TypeWrapper(object d)
 {
 this.dyn = d;
 Type type = d.GetType();
 foreach (var p in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
 {
 string name = p.Name;
 var wrappedObjectParameter = Expression.Parameter(typeof(object));
 var valueParameter = Expression.Parameter(typeof(object));
 var setExpression = Expression.Lambda<Action<object, object>>(
 Expression.Assign(
 Expression.Property(
 Expression.Convert(wrappedObjectParameter, type), p),
 Expression.Convert(valueParameter, p.PropertyType)),
 wrappedObjectParameter, valueParameter);
 this.setters.Add(name, setExpression.Compile());
 var getExpression = Expression.Lambda<Func<object, object>>(
 Expression.Convert(
 Expression.Property(
 Expression.Convert(wrappedObjectParameter, type), p),
 typeof(object)),
 wrappedObjectParameter);
 this.getters.Add(name, getExpression.Compile());
 }
 }
 public void Set(string name, object value)
 {
 var set = this.setters[name];
 set(this.dyn, value);
 }
 public object Get(string name)
 {
 var get = this.getters[name];
 return get(this.dyn);
 }
}
answered Apr 27, 2016 at 13:05
\$\endgroup\$
1
  • \$\begingroup\$ Ah excellent stuff. I had actually played about with Expressions in another approach but hadn't achieved the same performance. This is much faster. \$\endgroup\$ Commented Apr 28, 2016 at 0:07

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.