Linq的超越——强类型反射
Linq的超越——强类型反射文/Daniel Cazzulino 大家都知道Linq引入了标准查询操作符,从而使查询成为C#语言中最重要的概念。但不知您是否意识到,Linq还可用于查询外的其他用途。下面我将首次探讨Linq用于查询外的其他领域。反射问题:远离类型安全错误至少就我自己而言,使用C#这种类型安全语言时,每当按下Ctrl+Shift+B(或Shift+F6
·
Linq
的超越
——强类型反射
文/Daniel Cazzulino
大家都知道
Linq
引入了标准查询操作符
,从而
使查询成为
C#
语言中最重要的概念。但不知您是否意识到,
Linq
还可用于查询外的其他用途。下面我将首次探讨
Linq
用于查询外的其他领域。
反射问题
:远离类型
安全错误
至少就我自己而言
,
使用
C#
这种类型安全语言时,每当按下
Ctrl
+Shift+B
(
或
Shift
+F6
)
键
进行
编译时
,
会有一种轻松和放心的感觉。
我知道,由于使用错误的类型而产生的怪异且难于调试的运行时错误,以及像“方法缺失”这种提示几乎从未出现。但是使用反射时,如果我不小心,就会出现臭名昭著的
TargetInvocationException
和一些
NullReferenceException
之类的异常。
下面正是我所遇到的
:
MethodInfo method
=
typeof(Mock).GetMethod("PublicMethodParameters",
typeof(Mock).GetMethod("PublicMethodParameters",
newType[]{ typeof(string), typeof(int) }));
如果对
Mock
类应用重构以便重命名该方法
,
会发生什么情况呢
?
如果参数类型发生更改
,
会发生什么情况呢
?毫无疑问,会发生
运行时异常
!如果采用一个使用大量反射的插入式灵活框架,
这决不是一个小问题。由于害怕出错而不敢应用重构(或者使其代价昂贵)必然会限制您改进设计和完善代码的能力。
那么,试着替换魔力字符串(
magic strings
)和松散类型的Type数组将会如何呢?
MethodInfo info =
Reflector.Method<Mock, string, int>(
(x, y, z) => x.PublicMethodParameters(y, z));
通过Linq进行强类型反射
其工作原理是作为参数传递的
λ
表达式
(
就像前一版本
.NET
中的委托一样
)
不一定要执行。
上面的代码基本上构造了一个可以调用类型上给定方法的
λ
表达式。声明方法的目标类型的类型就是
Method<> static generic
方法的第一个类型参数。您可指定的可选类型参数将是您要调用的方法的参数类型(如果存在)。如果我想获得无参数方法的
MethodInfo
,则表达式将是:
MethodInfo
info
= Reflector.Method<Mock>(
x => x.PublicMethodNoParameters());
x => x.PublicMethodNoParameters());
这比您以前见到的任何
λ
表达式都典型。在
λ
表达式中,如果您需要传递附加参数,则必须将所有内容放到括号中(上例中的
x
、
y
和
z
)。
用于属性和字段的类型映射功能是相同的:
PropertyInfo
property
=
Reflector.Property<Mock>(x => x.PublicProperty);
Reflector.Property<Mock>(x => x.PublicProperty);
FieldInfo
field
=
Reflector.Field<Mock>(x => x.PublicField);
Reflector.Field<Mock>(x => x.PublicField);
利用表达式树
现在我们开始讨论比较有趣的内容,即如何实现它。
在
Linq
中
,
任何接收
λ
表达式
(
委托类型
)
的方法都可以转换为接收相同委托类型的
Expression<T>
的方法
,
并且不需要更改客户机代码。例如
:
privatestaticvoid
DoSomething(
Predicate<Mock> predicate)
可以替换为:
privatestaticvoid
DoSomething(
Expression<Predicate<Mock><Mock>> predicate)
Expression<Predicate<Mock><Mock>> predicate)
在上述两种情况下
,
调用代码可以是相同的
λ
表达式
:
DoSomething(x => x.Value > 25);
这里发生的情况是
,
编译器不会将指针传入到第二个方法签名的匿名委托中
,
而是生成以表达式树的形式构建
AST
(
抽象语法树
)
的
IL
代码。如果您打开
Reflector
(我的类型反射类的名字也由此而来,它是任何高级开发人员都应该经常使用的最伟大的工具)并取消对
DoSomething
的方法调用,就可以看到:
ParameterExpression
expression1
=
Expression.Parameter(typeof ( Mock ), "x");
Expression.Parameter(typeof ( Mock ), "x");
Program.DoSomething(
Expression.Lambda<Predicate<Mock>> (
Expression.GT(Expression.Field(
expression1, fieldof(Mock.Value)),
Expression.Lambda<Predicate<Mock>> (
Expression.GT(Expression.Field(
expression1, fieldof(Mock.Value)),
Expression.Constant(0x19, typeof(int))
),
newParameterExpression
[]{
expression1 })
)
;
这里您可以看到编译器如何使用
Expression
类
上的静态方法构建整个表达式
(
我对
API
的详细看法另外单独讨论
)
。当然,在方法实现中,您可以检查相同的树并执行任何想执行的操作。最新的
Linq CTP
包含一个非常酷的可视化工具,在运行时到达您的方法主体时可以用来查看表达式树中的情况:
到现在为止,您应该明白了我正在实现一个强类型反射
:
我接收一个表达式树
,
并在其中搜索方法调用节点
(
或者
,
对于属性和字段来说是成员访问
)
。下面是
Method<>
方法的实现:
publicstaticMethodInfo
Method
<
TDeclaringType
>
(
Expression<Operation> method)
Expression<Operation> method)
{
return
GetMethodInfo(method);
}
privatestaticMethodInfo
GetMethodInfo
(Expression
method
)
{
LambdaExpression
lambda
=
method
asLambdaExpression;
if
(lambda
== null
)
thrownewArgumentNullException ( "method" );
thrownewArgumentNullException ( "method" );
MethodCallExpression
methodExpr
= null;
//
我们的
Operation<T>
返回一个对象,
故首先可以声名一
// 个类型转换(如果方法无返回对象)或直接方法调用。
// 个类型转换(如果方法无返回对象)或直接方法调用。
if
(lambda.Body.NodeType
== ExpressionType.Cast)
{
//
类型转换是一个一元操作,而操作数是一个方法调用表达式。
methodExpr
=
((
UnaryExpression
)lambda.Body).
Operand asMethodCallExpression;
Operand asMethodCallExpression;
}
elseif
(lambda.Body.NodeType
== ExpressionType.MethodCall ||
lambda.Body.NodeType
== ExpressionType.MethodCallVirtual)
{
methodExpr = lambda.Body
asMethodCallExpression;
}
if
(methodExpr
== null)
thrownewArgumentException ( "method" );
thrownewArgumentException ( "method" );
return
methodExpr.Method;
}
我创建的就是
Operation
委托类型。不能使用
Linq Func<T>
(
以及
T
、
Arg0
……),
因为它们返回的是布尔值。我需要更灵活的对象,简单来说就是返回对象的对象,以及接收一些固定参数类型(例如
Func<T>
)的委托“重载”。因此我得到如下内容:
publicdelegateobjectOperation
();
publicdelegateobjectOperation<T>(T declaringType);
publicdelegateobjectOperation(T declaringType, A0 arg0);
...
注意
,
API
的用户从来都不会知道这些委托类型的对象的存在
,
就像查询操作符的用户从不知道
Func<T>
的存在一样。我希望将来这些委托能够消失,而代之以更好的东西(可能是
publicdelegateobject Operation < params T> ;)
)。此外,注意我是如何将新的参数类型的参数添加到T
“
后面
”
的,T是重载的通用转换,与
Linq
在
Func<T>
中的功能正好相反。
属性和字段与上面的例子非常类似
,
没有什么特殊之处
。不过,
Method
重载确实是一个很好的部分:
publicstaticMethodInfo
Method<TDeclaringType>(
Expression<Operation<TDeclaringType>> method)
Expression<Operation<TDeclaringType>> method)
{
return
GetMethodInfo(method);
}
publicstaticMethodInfo
Method<TDeclaringType, A0>
(
Expression<Operation<TDeclaringType, A0>> method)
Expression<Operation<TDeclaringType, A0>> method)
{
return
GetMethodInfo(method);
}
publicstaticMethodInfo
Method<TDeclaringType, A0, A1>(
Expression<Operation<TDeclaringType, A0, A1>> method)
Expression<Operation<TDeclaringType, A0, A1>> method)
{
return
GetMethodInfo(method);
}
...
好了
,
重点到底是什么
?
由于能够指定参数类型并在
λ
表达式中声明它们
,
您通常能够获得强类型和编译类型的安全性
,
即使您在结尾处使用了反射。回到最开始的例子:
MethodInfo
info
= Reflector.Method<Mock, string, int>(
(x, y, z) => x.PublicMethodParameters(y, z));
(x, y, z) => x.PublicMethodParameters(y, z));
看一下如何使
y
和
z
参数成为强类型参数,并用于在编译时找出
λ
表达式中
“
调用
”
的是哪个方法。完全更新
IDE
以便与
C# 3.0
一起使用时,给定参数的重命名重构工具可提供期望的结果:它在任何情况下都会重命名,就像您使用它时一样,即使对于反射也是如此!并且,如果您更改参数类型,代码将会编译失败!
更多推荐
所有评论(0)
您需要登录才能发言
加载更多