Вы задаете 2 вопроса:
- Какую скорость вы теряете?
- Есть ли более быстрый способ сделать это
Вопрос № 1:
Ответ на первый вопрос: это зависит. Написание кода проверки свойства вручную может быть в несколько раз быстрее, чем код отражения. Однако это не может быть проблемой в зависимости от того, как часто вызывается код. Если код вызывается не очень часто, вы не получите много за его оптимизацию. Однако, если это называется много, то оптимизация может дать вам большие улучшения скорости. Я бы запустил ваше приложение под профилировщиком (мне лично нравится Dot Trace от Jet Brain), чтобы увидеть, где на самом деле тратится время. Процент времени, проведенного внутри «GenerateDirtyPropertiesOnEntity», даст вам теоретический максимальный прирост производительности, который вы можете получить, оптимизируя метод. Если это окажется небольшим процентом, я просто оставлю код как есть.
Вопрос № 2
Я могу придумать 2 простых способа сделать это быстрее:
- Введите код сравнения свойств от руки.
- Используйте класс DynamicMethod для генерации кода сравнения
Я предполагаю, что вы не хотите делать # 1. Я выложу код, который показывает # 2 через секунду.
Обновление:
Вот код для генерации динамического метода
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>>));
}
}
Он принимает универсальный параметр T и возвращает делегата, который при заданных 2 экземплярах T возвращает список всех измененных свойств.
Чтобы повысить производительность, рекомендуется вызвать метод один раз и сохранить результат в статическом поле только для чтения. Примерно так будет работать:
class FooBar
{
static readonly Func<FooBar,FooBar, List<string>> s_dirtyChecker;
static FooBar()
{
s_dirtyChecker = Util.CreateDirtyChecker<FooBar>();
}
public List<string> GetDirtyProperties(Foobar other)
{
return s_dirtyChecker(this, other);
}
}