#c# #lambda #expression #func
#c# #лямбда #выражение #функция
Вопрос:
Я пытаюсь создать функцию для сравнения исходного и обновленного значения и установить исходное значение на обновленное, если оно отличается. Функция делает гораздо больше, поэтому я упростил, чтобы сосредоточиться на предмете:
public void Match<T>(Expression<Func<object>> original, Expression<Func<object>> updated)
{
var mex = original.Body as MemberExpression;
var funcOriginal = original.Compile();
var funcUpdated = updated.Compile();
var valueOriginal = funcOriginal();
var valueUpdated = funcUpdated();
if (valueOriginal != valueUpdated)
{
var info = mex.Member as PropertyInfo;
var target = ???; //How to get the original.TestProperty here?
info.SetValue(target, valueUpdated);
}
}
Я хотел бы вызвать так:
manager.Match<TestClass>(() => original.TestProperty, () => updated.TestProperty);
Ответ №1:
Expression
Свойство вашей информации о члене — это представление переменной, из которой извлекается свойство. Вам просто нужно вызвать это, чтобы оценить его значение.
public static void Match<T>(Expression<Func<T>> original,
Expression<Func<T>> updated)
{
var mex = original.Body as MemberExpression;
var valueOriginal = original.Compile()();
var valueUpdated = updated.Compile()();
if (!object.Equals(valueOriginal, valueUpdated))
{
var info = mex.Member as PropertyInfo;
var target = Expression.Lambda(mex.Expression).Compile().DynamicInvoke();
info.SetValue(target, valueUpdated);
}
}
Конечно, если этот тип является типом значения, то то, что вы будете создавать, вызывая это выражение, является копией этого значения, и в конечном итоге вы измените копию, но пока это ссылочный тип, вы будете копировать ссылку, и фактический объект фактически будетмутировал.
Однако есть еще один совершенно другой путь, которым вы можете воспользоваться. Вместо того, чтобы пытаться вычислить переменную, которой вы можете присвоить это свойство, вы можете просто создать выражение, представляющее присвоение значения, которое у вас есть для этого выражения. Этот код будет работать даже в том случае, если доступ к свойству осуществляется из типа значения, потому что он фактически изменяет переменную, а не получает значение этой переменной и изменяет его.
public static void Match<T>(Expression<Func<T>> original,
Expression<Func<T>> updated)
{
var mex = original.Body as MemberExpression;
var valueOriginal = original.Compile()();
var valueUpdated = updated.Compile()();
if (!object.Equals(valueOriginal, valueUpdated))
{
var body = Expression.Assign(
Expression.MakeMemberAccess(mex.Expression, mex.Member),
updated.Body);
Expression.Lambda<Action>(body).Compile().Invoke();
}
}
Вы также фактически не использовали свой общий аргумент для типа результата ваших двух функций, несмотря на то, что сделали метод универсальным.
Ответ №2:
Если вы хотите безопасность типов, вы можете передать оригинал
public void Match<TSource, TType>(TSource dest, Expression<Func<TSource, TType>> original, TType updateValue)
{
var originalValue = original.Compile()(dest);
if (!updateValue.Equals(originalValue))
{
// get prop name from expression
var prop = original.GetMemberInfo().Name;
typeof(TSource).GetProperty(prop).SetValue(dest, updateValue);
}
}
//helper class to get propinfo from expression
public static class ExpressionExtensions
{
public static MemberInfo GetMemberInfo(this Expression expression)
{
var lambda = (LambdaExpression) expression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = (UnaryExpression) lambda.Body;
memberExpression = (MemberExpression) unaryExpression.Operand;
}
else
memberExpression = (MemberExpression) lambda.Body;
return memberExpression.Member;
}
}
используйте его как
manager.Match(original, o=>o.TestProperty, updated.TestProperty);
Я не тестировал это, но должно сработать или приблизить вас.