You are asking 2 questions:
- How much speed are you loosing?
- Is there a faster way to do it
Question #1:
The answer to the first one is: it depends. Writing the property checking code by hand could be several times faster than the reflection code. However, that might not be actually be a problem depending on how often the code gets called. If the code isn't called very frequently, then you wouldn't get much for the trouble of optimizing it. If, however, it's called a lot, then optimizing it may give you big speed improvements. I would run your app underneath a profiler (I like Jet Brain's Dot Trace personally) to see where the time is actually being spent. The percentage of time spent inside "GenerateDirtyPropertiesOnEntity" will give you the theoretical maximum perf gain you can get by optimizing the method. If that ends up being a small percentage, then I would just keep the code as is.
Question #2
I can think of 2 simple ways of making this faster:
- Write the property comparison code by hand.
- Use the DynamicMethod class to generate the comparison code
I'm assuming you don't want to do #1. I'll post some code that shows #2 in a second.
Update:
Here's the code for generating a dynamic method
class Util
{
public static Func<T,T, List<string>> CreateDitryChecker<T>()
{
var dm =
new DynamicMethod
(
"$dirty_checker",
typeof(List<string>),
new[] { typeof(T), typeof(T) },
typeof(T)
);
var ilGen = dm.GetILGenerator();
//var retVar = new List<string>();
var retVar = ilGen.DeclareLocal(typeof(List<string>));
ilGen.Emit(OpCodes.Newobj, typeof(List<string>).GetConstructor(new Type[0]));
ilGen.Emit(OpCodes.Stloc, retVar);
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
MethodInfo objEqualsMehtod = typeof(object).GetMethod("Equals", new[] { typeof(object) });
MethodInfo listAddMethod = typeof(List<string>).GetMethod("Add");
foreach (PropertyInfo prop in properties)
{
//Inject code equivalent to the following into the method:
//if (arg1.prop == null)
//{
// if (arg2.prop != null)
// {
// retVar.Add("prop")
// }
//}
//else
//{
// if (! arg1.prop.Equals(arg2))
// {
// retVar.Add("prop")
// }
//}
Label endLabel = ilGen.DefineLabel();
Label elseLabel = ilGen.DefineLabel();
//if arg1.prop != null, goto elseLabel
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Call, prop.GetGetMethod());
ilGen.Emit(OpCodes.Brtrue, elseLabel);
//if arg2.prop != null, goto endLabel
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.EmitCall(OpCodes.Call, prop.GetGetMethod(), null);
ilGen.Emit(OpCodes.Brfalse, endLabel);
//retVar.Add("prop");
ilGen.Emit(OpCodes.Ldloc, retVar);
ilGen.Emit(OpCodes.Ldstr, prop.Name);
ilGen.EmitCall(OpCodes.Callvirt, listAddMethod, null);
ilGen.Emit(OpCodes.Br, endLabel);
//elseLabel:
ilGen.MarkLabel(elseLabel);
//if (arg0.prop.Equals(arg1.prop), goto endLabel
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.EmitCall(OpCodes.Call, prop.GetGetMethod(), null);
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.EmitCall(OpCodes.Call, prop.GetGetMethod(), null);
ilGen.EmitCall(OpCodes.Callvirt, objEqualsMehtod, null);
ilGen.Emit(OpCodes.Brtrue, endLabel);
//retVar.Add("prop")
ilGen.Emit(OpCodes.Ldloc, retVar);
ilGen.Emit(OpCodes.Ldstr, prop.Name);
ilGen.EmitCall(OpCodes.Callvirt, listAddMethod, null);
//endLAbel:
ilGen.MarkLabel(endLabel);
}
ilGen.Emit(OpCodes.Ldloc, retVar);
ilGen.Emit(OpCodes.Ret);
return (Func<T, T, List<string>>) dm.CreateDelegate(typeof(Func<T, T, List<string>>));
}
}
It takes in a generic parameter T and returns a delegate that, when given 2 T instances will return a list of all the changed properties.
To get a perf boost out of it, it's a good idea to call the method once and store the result in a readonly static field. Something like this would work:
class FooBar
{
static readonly Func> s_dirtyChecker;
static FooBar()
{
s_dirtyChecker = Util.CreateDirtyChecker<FooBar>();
}
public List<string> GetDirtyProperties(Foobar other)
{
return s_dirtyChecker(this, other);
}
}