I'm using a custom implementation of a DynamicObject which works perfectly for my application, other than the fact that I'm running into some performance issues. Some performance overhead is to be expected with dynamics, but I'm seeing significant (read: orders of magnitude) performance penalties even over using ExpandoObject.
The reason I can't use ExpandoObject is that I want to override some of its behavior. I've boiled the problem down to a very simple example below.
My custom ExpandoObject code is the following (simplified down to just enough code to exhibit the problem) --
public class SuperExpando : DynamicObject
{
public Dictionary<string, object> dictionary = new Dictionary<string, object>();
public override bool TrySetMember(SetMemberBinder binder, object value)
{
dictionary[binder.Name] = value;
return true;
}
}
public dynamic m = new SuperExpando();
When I set values in the DynamicObject's dictionary directly (i.e. m.dictionary["keyname"] = 500), then I see performance similar to that of ExpandoObject, which is sub-millisecond times to set the value of the key in the dictionary. When I use the TrySetMember override (i.e. m.keyname = 500), then I see performance drop to as much as 30ms - 50ms per key value set. When writing to lots of keys, this obviously becomes a problem. Even if I write to the same key over and over again, accessing it through TrySetMember takes the same amount of time.
My real performance problem doesn't appear to be related to the fact that I'm using dynamics as it does the TrySetMember override. For kicks I even commented out the
dictionary[binder.Name] = value;
in the TrySetMember method and left nothing but a "return true;", and the performance was the same.
If I add something like the following to my SuperExpando class --
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (dictionary.ContainsKey(binder.Name))
{
result = dictionary[binder.Name];
return true;
}
return false;
}
The performance issues accessing (reading) the variables through TryGetMember is the same, whereas reading the dictionary directly offers reasonable performance.
Any ideas?
-BJ Quinn
EDIT: Here's full sample code. Just create a form and put a button on it that runs the go_Click event and set your project to be a Console Application. For me it takes ~30ms to set all 50 keys in the ExpandoObject, whereas the SuperExpando takes a minimum of ~750ms.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Dynamic;
namespace test
{
public partial class ExpandoTest : Form
{
public ExpandoTest()
{
InitializeComponent();
}
public class SuperExpando : DynamicObject
{
public Dictionary<string, object> dictionary = new Dictionary<string, object>();
public override bool TrySetMember(SetMemberBinder binder, object value)
{
//dictionary[binder.Name] = value;
return true;
}
}
DateTime lasttime = DateTime.Now;
public void outputtime(string label = "")
{
TimeSpan elapsedtime = DateTime.Now - lasttime;
Double elapsedms = elapsedtime.TotalMilliseconds;
Console.WriteLine(label + " : " + elapsedms.ToString());
lasttime = DateTime.Now;
}
private void go_Click(object sender, EventArgs e)
{
outputtime("Time spent waiting on user");
dynamic se = new SuperExpando();
outputtime("Declared SuperExpando");
se.test120 = 5;
se.test121 = 5;
se.test122 = 5;
se.test123 = 5;
se.test124 = 5;
se.test125 = 5;
se.test126 = 5;
se.test127 = 5;
se.test128 = 5;
se.test129 = 5;
se.test130 = 5;
se.test131 = 5;
se.test132 = 5;
se.test133 = 5;
se.test134 = 5;
se.test135 = 5;
se.test136 = 5;
se.test137 = 5;
se.test138 = 5;
se.test139 = 5;
se.test140 = 5;
se.test141 = 5;
se.test142 = 5;
se.test143 = 5;
se.test144 = 5;
se.test145 = 5;
se.test146 = 5;
se.test147 = 5;
se.test148 = 5;
se.test149 = 5;
se.test150 = 5;
se.test151 = 5;
se.test152 = 5;
se.test153 = 5;
se.test154 = 5;
se.test155 = 5;
se.test156 = 5;
se.test157 = 5;
se.test158 = 5;
se.test159 = 5;
se.test160 = 5;
se.test161 = 5;
se.test162 = 5;
se.test163 = 5;
se.test164 = 5;
se.test165 = 5;
se.test166 = 5;
se.test167 = 5;
se.test168 = 5;
se.test169 = 5;
outputtime("Time to Run SuperExpando, set 50 test key/value pairs -- (not even setting values, just returning true from TrySetMember!)");
dynamic eo = new ExpandoObject();
outputtime("Declared ExpandoObject");
eo.test120 = 5;
eo.test121 = 5;
eo.test122 = 5;
eo.test123 = 5;
eo.test124 = 5;
eo.test125 = 5;
eo.test126 = 5;
eo.test127 = 5;
eo.test128 = 5;
eo.test129 = 5;
eo.test130 = 5;
eo.test131 = 5;
eo.test132 = 5;
eo.test133 = 5;
eo.test134 = 5;
eo.test135 = 5;
eo.test136 = 5;
eo.test137 = 5;
eo.test138 = 5;
eo.test139 = 5;
eo.test140 = 5;
eo.test141 = 5;
eo.test142 = 5;
eo.test143 = 5;
eo.test144 = 5;
eo.test145 = 5;
eo.test146 = 5;
eo.test147 = 5;
eo.test148 = 5;
eo.test149 = 5;
eo.test150 = 5;
eo.test151 = 5;
eo.test152 = 5;
eo.test153 = 5;
eo.test154 = 5;
eo.test155 = 5;
eo.test156 = 5;
eo.test157 = 5;
eo.test158 = 5;
eo.test159 = 5;
eo.test160 = 5;
eo.test161 = 5;
eo.test162 = 5;
eo.test163 = 5;
eo.test164 = 5;
eo.test165 = 5;
eo.test166 = 5;
eo.test167 = 5;
eo.test168 = 5;
eo.test169 = 5;
outputtime("Time to Run ExpandoObject, set 50 test key/value pairs");
}
}
}
ExpandoObject
doesn't inherit fromDynamicObject
directly so you aren't comparing apples to apples. Try havingSuperExpando
implementIDynamicMetaObjectProvider
instead if you want a true test. Even though this is true, you're writing a UI application... what difference does a few ms make? – Episcopalian