【问题标题】:Static method to swap the values of two objects' property values using Expressions使用表达式交换两个对象属性值的静态方法
【发布时间】:2018-11-17 00:09:21
【问题描述】:

我正在尝试制作一个实用函数,它可以交换两个 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);
}

事实证明,要找到表达式的“主题”对象很困难,尽管我很确定这是可能的。

【问题讨论】:

  • 只要属性具有可分配的类型,这是否应该适用于任何对象类型,或者此方法将交换相同类型对象的属性?
  • @EmrahSüngü 我在想它也可以处理具有任何两个不同属性的任何两个不同对象,只要两个属性是相同的类型。我们甚至可以更进一步说尝试交换任意两个值,如果一个运行时值不能分配给另一种属性类型,就让它爆炸。

标签: c# lambda reflection .net-4.5 expression-trees


【解决方案1】:

您可以使用 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);

【讨论】:

  • 是的,在我发布之前检查了我的环境
【解决方案2】:

我试图编写尽可能自我解释的代码,所以我会让 cmets 来说话。代码需要错误检查,但我会留给你。这是最低工作量。我为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);

【讨论】:

    猜你喜欢
    • 2020-07-24
    • 1970-01-01
    • 1970-01-01
    • 2019-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-08
    相关资源
    最近更新 更多