[翻译]使用表达式树获取对象、类型和成员的信息

原文来自Alexandra Rusina在CSharpFAQGetting Information About Objects, Types, and Members with Expression Trees

从C#3.0/Visual Studio 2008开始,你可以使用表达式树获取对象、类型和成员的信息。在这篇文章我将要展示一些例子并解释使用这个技巧能得到什么好处。如果你并不熟悉表达式树,我推荐你先阅读Charlie Calvert的文章表达式树基础[译文] [原文]

让我们从一个简单任务开始。假设你需要输出字段或属性的名字和它的值。例如,设想你有下面的类:

public class SampleClass
{
    
public string SomeField = "Test";
}

当然,这里有个简单方案:

SampleClass sample = new SampleClass();
Console.WriteLine("SomeField : {0}", sample.SomeField);
// 输出 SomeField : Test

上面代码的问题是,你使用了硬编码的字符串SomeField。没有任何保证这就是字段的真实名称。你可能敲错字母或是意外的指定了错误名称。另外,如果你重命名你的属性为AnotherField,必须手动找到所有表示它的名字的字符串。

这里告诉你,怎么创建一个使用表达式树返回一个属性名的方法,不需要使用任何字符串:

public static string GetName<T>(Expression<Func<T>> e)
{
    
var member = (MemberExpression)e.Body;
    
return member.Member.Name;
}

要调用此方法,你需要传递给它一个lambda表达式,如下所示:

Console.WriteLine("{0} : {1}"
    GetName(() => sample.SomeField), sample.SomeField);
// 同样输出 SomeField : Test

因为用lambda表达式,你不仅拥有编译时错误检查,而且在键入成员名称时有完全的智能感知支持。并且如果你重命名此属性,编译器将找出所有使用了此属性的地方。或者依靠重构工具来重命名属性所有的使用代码。

事实上,你同样可以使用这个方法来获得变量本身的名称(这对跟踪和记录日志都非常方便)。

Console.WriteLine("{0} : {1}", GetName(() => sample), sample);
//输出 sample : SampleClass

使用lambda表达式仅有的一个问题是你需要确保使用者输入正确。例如,我们的方法的使用者可以传递一个像这样的lambda表达式()=>new SampleClass()。很不幸,你无法为这得到编译检查。那么,当我们使用lambda表达式时,请确保你真的能得到你预期的表达式。例如像这样:

public static string GetName<T>(Expression<Func<T>> e)
{
    
var member = e.Body as MemberExpression;

    
// 如果当前方法得到一个不是成员访问的
    
// lambda表达式,
    
// 例如, () => x + y, 则抛出一个异常
    
if (member != null)
        
return member.Member.Name;
    
else
        
throw new ArgumentException(
            
"'" + e + 
            
"': 对此方法来讲不是有效的表达式");
}

另一场景是表达式树可以帮助你获得类型的成员信息。C#提供typeof操作符来获得类型信息,但没有提供像
memberofinfoof这样的操作符。我们可以使用反射方法比如Type.GetField(),Type.GetMethod(),等等,但这时你不得不又使用字符串。

// 使用typeof操作符,你可以在不使用字符串的情况下得到类型信息
Type type = typeof(SampleClass);

// 但当使用反射获取成员信息,你不得不依赖字符串
FieldInfo fieldInfo = type.GetField("SomeField");

Console.WriteLine(fieldInfo);
// 输出 System.String SomeField

如果在你的类里使用了重载,那么问题变得更糟糕。

public class SampleClass
{
    
public string SomeField = "Test";
    
public void SomeMethod() { }
    
public void SomeMethod(string arg) { }
}

在这里,你需要指定方法名和方法参数。

MethodInfo methodInfo = type.GetMethod(
    
"SomeMethod"new Type[] { typeof(String) });
Console.WriteLine(methodInfo);
// 输出 Void SomeMethod(System.String)

现在你有很多情况让你陷入困境。除了明确的指定你的方法名,你还得指定参数的数量和类型。那么方法签名的任何改变都能影响你程序的行为,而编译器不会察觉到。

你可以通过表达式树获得相同的信息而不需要硬编码字符串,同时使编译器检查是否存在你需要的成员。此外,要获取所需的重载方法只要在lambda表达式中提供此方法的使用实例。
谢谢Mads Torgersen,一个Visual Studio Program Manager,提供下面的代码样例。

 

public static MemberInfo MemberOf<T>(Expression<Func<T>> e)
{
    
return
 MemberOf(e.Body);
}

// 
我们需要添加这个重载来覆盖方法没有返回值的情况

public static MemberInfo MemberOf(Expression<Action> e)
{
    
return
 MemberOf(e.Body);
}

private
 static MemberInfo MemberOf(Expression body)
{
    {
        
var
 member = body as MemberExpression;
        
if (member != null) return member.Member;
    }

    {
        
var
 method = body as MethodCallExpression;
        
if (method != null) return method.Method;
    }

    
throw
 new ArgumentException(
        
"'" + body + "': 不是一个成员访问");
}

 

现在你可以在所有场合使用MemberOf方法,实例成员和静态成员都行。

 

Console.WriteLine(MemberOf(() => sample.SomeField));
// 输出 System.String SomeField

// 用来选择一个特定方法重载,你在这里简单的展示方法的用法
Console.WriteLine(MemberOf(() => sample.SomeMethod("Test")));
// 输出 Void SomeMethod(System.String)

Console.WriteLine(MemberOf(() => sample.SomeMethod()));
// 输出 Void SomeMethod()

Console.WriteLine(MemberOf(() => Console.Out));
// 输出 System.IO.TextWriter Out

 

我想重申一下,这个功能已经在C#3.0/.NET framework 3.5有效。如果你想知道表达式树在.NET framework 4里做了怎样的扩展,看看我之前的文章: Generating Dynamic Methods with Expression Trees in Visual Studio 2010.


posted @ 2010-03-07 21:10 甜番薯 阅读(...) 评论(...) 编辑 收藏