在开发过程中,经常需要处理一些复杂的逻辑,如属性更改通知、参数验证、模拟对象创建等。Linq.Expression 提供了一种强大的方式来简化这些操作。本文将通过五个实际案例,展示如何使用 Linq.Expression 来实现这些功能。
在 WPF 或其他 MVVM 框架中,经常需要实现 INotifyPropertyChanged 接口来通知属性更改。传统的实现方式需要编写大量的模板代码,但通过使用 Linq.Expression,可以简化这个过程。
首先,定义一个基类 PropertyChangeBase,它实现了INotifyPropertyChanged接口,并提供了一个 SignalChanged 方法。
public class PropertyChangeBase : INotifyPropertyChanged
{
protected void SignalChanged<T>(Expression<Func<T>> exp)
{
if (exp.Body.NodeType == ExpressionType.MemberAccess)
{
var name = ((MemberExpression)exp.Body).Member.Name;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
else
{
throw new Exception("Unexpected expression");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
然后,可以通过继承这个基类,并调用 SignalChanged 方法来实现属性更改通知。
public class Customer : PropertyChangeBase
{
private string _customerName;
public string CustomerName
{
get { return _customerName; }
set
{
if (value != _customerName)
{
_customerName = value;
SignalChanged(() => CustomerName);
}
}
}
}
这种方式的好处是,它支持 IntelliSense,并且对重构友好,可以轻松地更改属性名称而不需要修改 SignalChanged 方法。
在编写方法时,经常需要对参数进行验证。传统的验证方式需要硬编码参数名称,这在重构时会带来麻烦。通过使用 Linq.Expression,可以避免这个问题。
public void DoSomething(int arg1)
{
Contract.Expect(() => arg1).IsGreaterThan(0).IsLessThan(100);
}
这种方式的好处是,它支持 IntelliSense,并且对重构友好,可以轻松地更改参数名称而不需要修改验证逻辑。
MoQ 是一个 .NET 库,用于创建模拟对象。它内部使用 Linq.Expression 来实现可读性强的语法。
mock.Setup(framework => framework.DownloadExists("2.0.0.0")).Returns(true).AtMostOnce();
这种方式的好处是,它支持 IntelliSense,并且可以轻松地创建模拟对象。
在 C# 中,可以使用 ref 关键字来交换两个变量的值。但是,如果想要交换对象的属性或数组的元素,就需要编写更复杂的代码。通过使用 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:
AssignTo(rvalue, left.Body as BinaryExpression);
break;
case ExpressionType.Call:
AssignTo(rvalue, left.Body as MethodCallExpression);
break;
default:
AssignTo(left, rvalue);
break;
}
switch (right.Body.NodeType)
{
case ExpressionType.ArrayIndex:
AssignTo(lvalue, right.Body as BinaryExpression);
break;
case ExpressionType.Call:
AssignTo(lvalue, right.Body as MethodCallExpression);
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) => 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()();
}
}
这种方式的好处是,它支持 IntelliSense,并且可以轻松地交换对象的属性或数组的元素。
在开发过程中,经常需要对类的方法进行单元测试,以确保它们具有特定的属性。通过使用 Linq.Expression,可以编写简洁的测试代码。
var controller = new HomeController();
controller.ShouldHave(x => x.Index(), typeof(AuthorizeAttribute));