65.9K
CodeProject 正在变化。 阅读更多。
Home

(5) Linq.Expression 的有趣用法

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2012年1月24日

CPOL

2分钟阅读

viewsIcon

13571

以下列出 5 个有趣的 Linq.Expression 用法,当然,不包括编写 LinqToSomething 提供程序。

… 不包括编写 LinqToSomething 提供程序。Expression.<Func<T>> 构造有时会让人感到害怕,因为我们认为需要编写一些复杂的树形导航才能实现表达式行为,但事实并非总是如此。在某些情况下,我们可以使用它而无需进行任何复杂的树形访问。 在本文中,我们将看到使用这种策略的一些实际示例。

1) INotifyPropertyChanged 无“魔术字符串”

该接口以最简单的形式实现如下

public string CustomerName
        {
            get
            {
               return this.customerNameValue;
            }
            set
            {
               if (value != this.customerNameValue)
               {
                   this.customerNameValue = value;
                   NotifyPropertyChanged("CustomerName");
               }
           }
       }

我们可以通过这个简单的基类利用 Linq.Expression

class PropertyChangeBase : INotifyPropertyChanged
    {
        protected void SignalChanged<T>(Expression<Func<T>> exp)
        {
            if (exp.Body.NodeType == ExpressionType.MemberAccess)
            {
                var name = (exp.Body as MemberExpression).Member.Name;
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
           else
               throw new Exception("Unexpected expression");
       }
       #region INotifyPropertyChanged Members
       public event PropertyChangedEventHandler PropertyChanged = delegate { };
       #endregion
   }

通过从这个类派生,我们可以很容易地通过编写以下代码来通知属性更改

SignalChanged(()=>CustomerName);

这使我们能够利用 Intellisense,并且易于重构,因此我们可以毫不费力地更改属性的名称。 我第一次看到使用这种技术的项目是 Caliburn Micro,但我不确定它是否是唯一的也是第一个。 相同的技术用于测试 INotifyPropertyChange 行为

2) 参数验证

与上述问题非常相似,我们希望避免

 static int DivideByTwo(int num) 
   {
       // If num is an odd number, throw an ArgumentException.
       if ((num & 1) == 1)
           throw new ArgumentException("Number must be even", "num");
       // num is even, return half of its value.
       return num / 2;
   }

在这种情况下,我们正在键入 NUM,即参数的名称,作为字面 string ,这很糟糕。 我们更希望编写如下内容

public void DoSomething(int arg1) 
    { Contract.Expect(() => arg1).IsGreatherThan(0) .IsLessThan(100); ; } 

这再次为我们提供了Intellisense 和重构感知您可以在这里找到此辅助类的代码,以及简要描述 在此帖子中

3) MoQ 模拟库

MoQ 库是一个 .NET 库,用于轻松创建模拟对象,它内部利用 Linq.Expression 来实现如此易读的语法

 mock.Setup(framework => framework.DownloadExists("2.0.0.0"))
     .Returns(true)
     .AtMostOnce();

4) 通用交换函数

在 C# 中创建通用交换函数的 simplest 方法是

void Swap<T>(ref T a, ref T b)
{
   T temp = a;
   a = b;
   b = temp;
}

不幸的是,如果我们要交换对象的两个属性,或者数组的两个元素,这不起作用。 我们希望编写如下内容

var t = new Test_() { X = 0, Y = 1 };
Swapper.Swap(() => t.X, () => t.Y);
Assert.AreEqual(0, t.Y);
Assert.AreEqual(1, t.X);

或者对于数组

int[] array = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Swapper.Swap(() => array[0], () => array[1]);
Assert.AreEqual(2, array[0]);
Assert.AreEqual(1, array[1]);

我们可以通过使用 Linq.Expression 的简单辅助类来实现这一点

public class Swapper
    {
        public static void Swap<T>(Expression<Func<T>> left, Expression<Func<T>> right)
        {
            var lvalue = left.Compile()();
            var rvalue = right.Compile()();
            switch (left.Body.NodeType)
            {
               case ExpressionType.ArrayIndex:
                   var binaryExp = left.Body as BinaryExpression;
                   AssignTo(rvalue, binaryExp);
                   break;

               case ExpressionType.Call:
                   var methodCall = left.Body as MethodCallExpression;
                   AssignTo(rvalue, methodCall);
                   break;

               default:
                   AssignTo(left, rvalue);
                   break;
           }

           switch (right.Body.NodeType)
           {
               case ExpressionType.ArrayIndex:
                   var binaryExp = right.Body as BinaryExpression;
                   AssignTo(lvalue, binaryExp);
                   break;

               case ExpressionType.Call:
                   var methodCall = right.Body as MethodCallExpression;
                   AssignTo(lvalue, methodCall);
                   break;

               default:
                   AssignTo(right, lvalue);
                   break;
           }
       }

       private static void AssignTo<T>(T value, MethodCallExpression methodCall)
       {
           var setter = GetSetMethodInfo
        (methodCall.Method.DeclaringType,methodCall.Method.Name);
           Expression.Lambda<Action>(
               Expression.Call(methodCall.Object, setter, 
               Join(methodCall.Arguments, Expression.Constant(value)))
           ).Compile()();
       }

       private static Expression[] Join(ReadOnlyCollection<Expression> args,Expression exp)
       {
           List<Expression> exps = new List<Expression>();
           exps.AddRange(args);
           exps.Add(exp);
           return exps.ToArray();
       }

       private static MethodInfo GetSetMethodInfo(Type target, string name)
       {
           var setName = Regex.Replace(name, "get", new MatchEvaluator((m) =>
           {
               return m.Value.StartsWith("g")?"set":"Set";
           })
           ,RegexOptions.IgnoreCase);
           var setter = target.GetMethod(setName);
           if (null == setter)
           {
               throw new Exception("can't find an expected method named:" + setName);
           }
           return setter;
       }

       private static void AssignTo<T>(Expression<Func<T>> left, T value)
       {
           Expression.Lambda<Func<T>>(Expression.Assign
           (left.Body, Expression.Constant(value))).Compile()();
       }
       private static void AssignTo<T>(T value, BinaryExpression binaryExp)
       {
           Expression.Lambda<Func<T>>(Expression.Assign
           (Expression.ArrayAccess(binaryExp.Left, binaryExp.Right), 
           Expression.Constant(value))).Compile()();
       }
   }

此代码利用了 Takeshi Kiriya 的一个示例,我只是在原始代码中添加了处理数组的能力 在他的代码中

5) 单元测试属性的存在

Thomas Ardal这篇文章中介绍了如何轻松单元测试类的方法上的属性是否存在,例如在 MVC 场景中,或其他 AOP 环境中。

利用他的策略编写的测试如下

 var controller = new HomeController();
 controller.ShouldHave(x => x.Index(), typeof(AuthorizeAttribute));

因此,我们展示了五个不同的简单应用,希望您在这里找到一些灵感,并随时分享您自己的想法来丰富此列表。


© . All rights reserved.