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);
}
}
1 Answer 1
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);
}
}
-
\$\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– James South2016年04月28日 00:07:39 +00:00Commented Apr 28, 2016 at 0:07
Explore related questions
See similar questions with these tags.