[翻译]使用表达式树获取对象、类型和成员的信息
2010-03-07 21:10
429 查看
原文来自Alexandra Rusina在CSharpFAQ的Getting 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操作符来获得类型信息,但没有提供像memberof或infoof这样的操作符。我们可以使用反射方法比如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.
从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操作符来获得类型信息,但没有提供像memberof或infoof这样的操作符。我们可以使用反射方法比如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.
相关文章推荐
- 使用表达式树访问对象、类型及成员(上):获取类型和成员的信息
- 使用表达式树访问对象、类型及成员(下):获取对象和属性的值
- java基础-反射2(反射,反射操作对象,Class对象的使用,类型信息的获取)
- 使用反射技术获取指定类型中方法的完整信息
- 使用SYSTEMINFO类获取UNITY3D运行设备的各类信息(CPU类型,显卡类型等)
- 使用 Unity(二):配置 Unity 、读取配置信息和获取对象
- 获取关于 ResultSet 对象中列的类型和属性信息的对象
- 《Entity Framework 6 Recipes》中文翻译系列 (40) ------ 第七章 使用对象服务之从跟踪器中获取实体与从命令行生成模型(想解决EF第一次查询慢的,请阅读)
- 使用反射获取当前new的对象的 带泛型的父类的 类型
- 使用 Unity(二):配置 Unity 、读取配置信息和获取对象
- Java使用反射来获取成员变量泛型信息
- 获取对象类型信息+查看对象类型的结构+设置信息深度
- 缓存对象类型信息与使用GetType、typeof()的性能比较
- Spring JDBC-使用Spring JDBC获取本地连接对象以及操作BLOB/CLOB类型数据
- java反射学习笔记(3)---使用Class来获取方法、成员变量、构造函数信息
- 使用 Unity(二):配置 Unity 、读取配置信息和获取对象
- 【python】获取对象的类型和信息
- Python中继承、多态、多继承、判断类型、json.load()解析、获取/设置对象信息
- 【转】使用SYSTEMINFO类获取UNITY3D运行设备的各类信息(CPU类型,显卡类型等)