我正在尝试制作一个实用函数,它可以交换由两个lambda表达式给定的两个属性值-假设两个表达式都指示具有getter和setter的属性:
Swap(() => John.Lunch, () => Jimmy.Lunch);
我想这个方法应该是这样的,但我很难把它拼凑起来。
private static void Swap<TProperty>(
Expression<Func<TProperty>> first,
Expression<Func<TProperty>> second)
{
PropertyInfo firstProp = (PropertyInfo)((MemberExpression)first.Body).Member;
PropertyInfo secondProp = (PropertyInfo)((MemberExpression)second.Body).Member;
object firstObj = (((first.Body as MemberExpression).Expression as MemberExpression)
.Expression as ConstantExpression).Value;
object secondObj = (((second.Body as MemberExpression).Expression as MemberExpression)
.Expression as ConstantExpression).Value;
TProperty temp = (TProperty)firstProp.GetValue(firstObj);
firstProp.SetValue(firstObj, secondProp.GetValue(secondObj));
secondProp.SetValue(secondObj, temp);
}
事实证明,要找到表达的"主题"对象是很困难的,尽管我很确定这是可能的。
您可以用Expression
树编写交换本身:
private static void Swap<TProperty>(
Expression<Func<TProperty>> first,
Expression<Func<TProperty>> second)
{
var firstMember = first.Body as MemberExpression;
var secondMember = second.Body as MemberExpression;
var variable = Expression.Variable(typeof(TProperty));
var firstMemberAccess = Expression.MakeMemberAccess(firstMember.Expression, firstMember.Member);
var secondMemberAccess = Expression.MakeMemberAccess(secondMember.Expression, secondMember.Member);
var block = Expression.Block(new []{ variable },
Expression.Assign(variable, firstMemberAccess),
Expression.Assign(firstMemberAccess, secondMemberAccess),
Expression.Assign(secondMemberAccess, variable)
);
Expression.Lambda<Action>(block).Compile()();
}
示例:
class A { public int P { get; set; } }
class B { public int P2 { get; set; } }
var a = new A { P = 5 };
var b = new B { P2 = 10 };
Swap(() => a.P, () => b.P2);
我试着写尽可能不言自明的代码,所以我会让注释来说话。代码需要进行错误检查,但我将由您决定。这是最低工作条件。我为Swapper
选择了一个静态类,但您可以选择用作实例对象,然后也使用DI容器。
public static class Swapper
{
/// <summary>
/// Key used for look-ups.
/// </summary>
private struct Key
{
private readonly Type _tt1;
private readonly Type _tt2;
private readonly MemberInfo _m11;
private readonly MemberInfo _m12;
public Key(Type t1, Type t2, MemberInfo m1, MemberInfo m2)
{
_tt1 = t1;
_tt2 = t2;
_m11 = m1;
_m12 = m2;
}
public override bool Equals(object obj)
{
switch (obj)
{
case Key key:
return _tt1 == key._tt1 && _tt2 == key._tt2 && _m11 == key._m11 && _m12 == key._m12;
default:
return false;
}
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (_tt1 != null ? _tt1.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (_tt2 != null ? _tt2.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (_m11 != null ? _m11.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (_m12 != null ? _m12.GetHashCode() : 0);
return hashCode;
}
}
}
/// <summary>
/// This is the cache used for compiled actions. Because compiling is time consuming, I think it is better to cache.
/// </summary>
private static readonly ConcurrentDictionary<Key, object> CompiledActionsCache = new ConcurrentDictionary<Key, object>();
/// <summary>
/// Main Function which does the swapping
/// </summary>
public static void Swap<TSource, TDestination, TPropertySource>(TSource source, TDestination destination, Expression<Func<TSource, TPropertySource>> first, Expression<Func<TDestination, TPropertySource>> second)
{
//Try to get value from the cache or if it is cache miss then use Factory method to create Compiled Action
var swapper = (Action<TSource, TDestination>)CompiledActionsCache.GetOrAdd(GetKey(first, second), k => Factory(first, second));
//Do the actual swapping.
swapper(source, destination);
}
/// <summary>
/// Our factory method which does all the heavy lfiting fo creating swapping actions.
/// </summary>
private static Action<TSource, TDestination> Factory<TSource, TDestination, TPropertySource>(Expression<Func<TSource, TPropertySource>> first, Expression<Func<TDestination, TPropertySource>> second)
{
//////////////This is our aim/////////////
//// var temp = obj1.Property; /////
//// obj1.Property = obj2.Property; /////
//// obj2.Property = temp; /////
//////////////////////////////////////////
// Temp value for required for swapping
var tempValue = Expression.Variable(typeof(TPropertySource), "temp_value");
// Expression assignment
// first.body is already accesing property, just use it as it is :)
var assignToTemp = Expression.Assign(tempValue, first.Body);
// Expression assignment
// second.body is already accesing property, just use it as it is :)
var secondToFirst = Expression.Assign(first.Body, second.Body);
// Final switch here
var tempToSecond = Expression.Assign(second.Body, tempValue);
// Define an expression block which has all the above assignments as expressions
var blockExpression = Expression.Block(new[] { tempValue }, assignToTemp, secondToFirst, tempToSecond);
// An expression itself is not going to swap values unless it is compiled into a delegate
// (obj1, obj2) => blockExpression from the previous line
return Expression.Lambda<Action<TSource, TDestination>>(blockExpression, first.Parameters[0], second.Parameters[0]).Compile();
}
/// <summary>
/// Key creator method.
/// </summary>
private static Key GetKey<TSource, TDestination, TPropertySource>(Expression<Func<TSource, TPropertySource>> first, Expression<Func<TDestination, TPropertySource>> second)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var sourcePropertyInfo = ((MemberExpression)first.Body).Member;
var destinationPorpertyInfo = ((MemberExpression)second.Body).Member;
return new Key(sourceType, destinationType, sourcePropertyInfo, destinationPorpertyInfo);
}
}
让我们看看如何在中使用Swapper
public class From
{
public byte FromProperty { get; set; }
}
public class To
{
public byte ToProperty { get; set; }
}
var from = new From();
from.FromProperty = 5;
var to = new To();
Swapper.Swap(from, to, f => f.FromProperty, t => t.ToProperty);