5
\$\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);
    }
}
\$\endgroup\$
9
\$\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);
    }
}
\$\endgroup\$
  • \$\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\$ – James South Apr 28 '16 at 0:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.