(5) Linq.Expression 的有趣用法





5.00/5 (3投票s)
以下列出 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));
因此,我们展示了五个不同的简单应用,希望您在这里找到一些灵感,并随时分享您自己的想法来丰富此列表。