1. 基本了解
1.1 反射概述
文字说明
审查元数据并收集关于它的类型信息的能力称为反射,其中元数据(编译以后的最基本数据单元)就是一大堆的表,当编译程序集或者模块时,编译器会创建一个类定义表,一个字段定义表,和一个方法定义表等
反射提供了封装程序集、模块和类型的对象(Type
类型),可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了属性,可以利用反射对它们进行访问
实际上
反射是微软封装的一个帮助类库:using System.Reflection;
1.2 反射用途
- 使用
Assembly
定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例 - 使用
Module
了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法 - 使用
ConstructorInfo
了解构造函数的名称、参数、访问修饰符(如pulic
或private
)和实现详细信息(如abstract
或virtual
)等;使用Type
的GetConstructors
或GetConstructor
方法来调用特定的构造函数 - 使用
MethodInfo
了解方法的名称、返回类型、参数、访问修饰符(如pulic
或private
)和实现详细信息(如abstract
或virtual
)等;使用Type
的GetMethods
或GetMethod
方法来调用特定的方法 - 使用
FiedInfo
了解字段的名称、访问修饰符(如public
或private
)和实现详细信息(如static
)等,并获取或设置字段值。 - 使用
EventInfo
了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。 - 使用
PropertyInfo
了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值 - 使用
ParameterInfo
了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等
1.3 反射常用类
反射是一个程序集发现及执行的过程,通过反射能够得到 .exe
或.dll
等程序集内部的信息,使用反射能够看到一个程序集内部的接口、类、方法、字段、属性、特性等信息
类型 | 描述 |
---|---|
Assembly | 通过此类能够载入操纵一个程序集,并获取程序集内部信息 |
FieldInfo | 该类保存给定的字段信息 |
MethodInfo | 该类保存给定的方法信息 |
MemberInfo | 该类是一个基类,定义了EventInfo,FieldInfo,MethodInfo,PropertyInfo的多个公用行为 |
Module | 该类能够使你能訪问多个程序集中的给定模块 |
ParameterInfo | 该类保存给定的參数信息 |
PropertyInfo | 该类保存给定的属性信息 |
2. Assembly 程序集对象
2.1 对象简介
官方文档
程序集包含模块、模块包含类型,而类型包含成员。 反射提供封装程序集、模块和类型的对象。 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型
其它文档
System.Reflection.Assembly
:表示一个程序集
程序集是代码进行编译是的一个逻辑单元,把相关的代码和类型进行组合,然后生成PE文件(例如可执行文件.exe
和类库文件.dll
)
由于程序集在编译后并不一定会生成单个文件,而可能会生成多个物理文件,甚至可能会生成分布在不同位置的多个物理文件,所以程序集是一个逻辑单元,而不是一个物理单元;即程序集在逻辑上是一个编译单元,但在物理储存上可以有多种存在形式
对于静态程序集可以生成单个或多个文件,而动态程序集是存在于内存中的
在C#中程序集处处可见,因为任何基于.NET的代码在编译时都至少存在一个程序集(所有.NET项目都会默认引用mscorlib
程序集)
程序集包含了两种文件:可执行文件(.exe
文件)和 类库文件(.dll
文件)
在VS开发环境中,一个解决方案可以包含多个项目,而每个项目就是一个程序集
2.2 应用程序结构
包含应用程序域(AppDomain
),程序集(Assembly
),模块(Module
),类型(Type
),成员(EventInfo、FieldInfo、MethodInfo、PropertyInfo
) 几个层次
2.3 静态方法
常用静态方法
方法 | 返回值类型 | 描述 |
---|---|---|
Assembly.Load | Assembly | 加载相对路径下指定名称程序集 |
Assembly.LoadFile | Assembly | 根据全路径获取指定程序集 |
Assembly.LoadFrom | Assembly | 根据全路径获取指定程序集 |
2.4 实例方法,属性
常用实例属性
属性 | 属性值类型 | 描述 |
---|---|---|
assembly.FullName | string | 获取程序集的显示名称 |
assembly.Location | string | 获取程序集的完整路径(全名称) |
assembly.DefinedTypes | IEnumerable | 获取定义在程序集中类型集合 |
assembly.Modules | IEnumerable | 获取定义在程序集中模块集合 |
常用实例方法
方法 | 返回值 | 描述 |
---|---|---|
assembly.GetTypes() | Type[] | 获取程序集中定义的类型数组 |
assembly.GetType() | Type | 获取程序集中定义的类型 |
assembly.GetExportedTypes() | Type[] | 获取程序集中定义的所有公共类型(类,接口,枚举等) |
assembly.CreateInstance() | object | 根据传入类型创建类型实例 |
2.5 示例:加载程序集
方式一:
Load
,c2
引用了Helper
,有引用关系
using System;
using System.Reflection;
using Helper;
namespace c2
{
class Program
{
static void Main(string[] args)
{
// 相对路径下加载指定名称程序集文件
Assembly assembly = Assembly.Load("Helper");
}
}
}
示例二:
LoadFile
,c2
与taskdao
无引用关系
using System;
using System.Reflection;
namespace c2
{
class Program
{
static void Main(string[] args)
{
// 根据全名称(路径+文件名.后缀)下加载指定名称程序集文件
Assembly assembly =
Assembly.LoadFile(@"E:\SolutionZX\taskdao\bin\Debug\taskdao.dll");
}
}
}
示例三:
LoadFrom
,c2
与taskdao
无引用关系
using System;
using System.Reflection;
namespace c2
{
class Program
{
static void Main(string[] args)
{
// 根据全名称(路径+文件名.后缀)下加载指定名称程序集文件
Assembly assembly =
Assembly.LoadFrom(@"E:\SolutionZX\taskdao\bin\Debug\taskdao.dll");
}
}
}
示例四:根据类型创建类型实例,
c2
与taskdao
无引用关系
dynamic
类型为动态类型,使用时编译器不会检查(运行时检查)
using System;
using System.Reflection;
namespace c2
{
class Program
{
static void Main(string[] args)
{
// 根据全名称(路径+文件名.后缀)下加载指定名称程序集文件
Assembly assembly =
Assembly.LoadFrom(@"E:\SolutionZX\taskdao\bin\Debug\taskdao.dll");
// object _t = assembly.CreateInstance("task1dao.task1");
// 报错,object类型识别不出Show方法,因为C#是强类型语言
// _t.Show();
dynamic _t = assembly.CreateInstance("task1dao.task1");
_t.Show();
}
}
}
2.6 获取类型
获取普通类型
Assembly assembly = typeof(Program).Assembly;
Type type = assembly.GetType("c2.UserInfo");
获取泛型类型
Assembly assembly = typeof(Program).Assembly;
Type type = assembly.GetType("c2.UserInfo`1"); // UserInfo`1 英文状态下数字1左边符号,参数个数
3. Type 类型
在C#中可以理解为一个类对应一个Type
对象
3.1 实例属性,方法
实例属性
属性 | 属性值类型 | 描述 |
---|---|---|
type.Name | string | 获取类型名称(类名) |
type.FullName | string | 获取类全名(命名空间+类名称) |
type.Namespace | string | 获取类所在的命名空间 |
type.Assembly | string | 获取类所在程序集名称 |
type.BaseType | Type | 获取基类(父类) |
type.IsSubclassOf(Type parent) | bool | type是否是parent的子类 |
type.IsInstanceOfType(object o) | bool | o是否是type类的对象 |
type.IsClass | bool | 获取对象类型是否是类 |
type.IsInterface | bool | 获取对象类型是否是接口 |
type.IsNotPublic | bool | 获取对象类型是否公开 |
type.IsAbstract | bool | 获取对象类型是否是抽象的 |
type.IsSealed | bool | 获取对象类型是否是密封的 |
type.IsArray | bool | 获取对象类型是否是数组 |
type.IsEnum | bool | 获取对象类型是否是枚举 |
实例方法
方法 | 返回值类型 | 描述 |
---|---|---|
type.GetMembers() | MemberInfo[] | 获取类型中所有公共成员 |
type.GetMethods() | MethodInfo[] | 获取所有公共方法(包含基类) |
type.GetConstructors() | ConstructorInfo[] | 获取类型中所有公共构造函数 |
type.GetFields() | FieldInfo[] | 获取所有公共字段 |
type.GetProperties() | PropertyInfo[] | 获取所有公共属性 |
type.GetInterfaces() | Type[] | 获取所有公共接口 |
type.MakeGenericType(...) | Type | 设置泛型类,泛型参数类型 |
type.GetMember(...) | MemberInfo[] | 多个,获取公共成员(不常用) |
type.GetMethod(...) | MethodInfo | 单个,获取公共方法 |
type.GetConstructor(...) | ConstructorInfo | 单个,获取公共方法 |
type.GetField(...) | FieldInfo | 单个,获取公共字段 |
type.GetProperty(...) | PropertyInfo | 单个,获取公共属性 |
type.GetInterface(...) | Type | 单个,获取公共接口 |
3.2 操作示例一
public class Base
{
}
public interface Inta { }
public interface Intb { }
public class UserInfo<A> : Base, Inta, Intb
{
#region 公共构造函数
public UserInfo()
{
Console.WriteLine("无参构造方法...");
}
public UserInfo(int id)
{
Console.WriteLine("1个参数构造方法");
}
#endregion
#region 私有构造函数
private UserInfo(string name) { }
#endregion
#region 公共字段
public string code;
#endregion
#region 私有字段
private string msg;
#endregion
#region 公共属性
public int id { get; set; }
#endregion
#region 公共方法
public void Print()
{
Console.WriteLine("无参数实例方法");
}
public void Show()
{
Console.WriteLine("无参数重载实例方法");
}
public void Show(int id)
{
Console.WriteLine("有参数重载实例方法-" + id.ToString());
}
#endregion
#region 公共静态方法
public static void Statc() { }
#endregion
#region 私有方法
private void GetM()
{
Console.WriteLine("无参数私有方法");
}
private void GetM(int i)
{
Console.WriteLine("有参数私有方法-" + id.ToString());
}
#endregion
#region 公共泛型方法
public void GenericC(A a)
{
Console.WriteLine("公共泛型无参方法:" + a.ToString());
}
public void GenericS<T>()
{
Console.WriteLine("公共泛型无参方法");
}
public void GenericsA<T>(A a, T t)
{
Console.WriteLine("公共泛型有参方法:" + t.ToString() + "\t" + a.ToString());
}
#endregion
}
通过类获得对应的
Type
Type type = typeof(UserInfo);
通过
Assembly
对象,通过类的fullname
类获得Type
对象
Assembly assem = Assembly.LoadFrom(@"E:\SolutionRP\DMO\bin\Debug\DMO.dll");
Type type = assem.GetType("DMO.UserInfo");
综合示例
Type type = typeof(UserInfo);
Console.WriteLine("类型名:" + type.Name);
Console.WriteLine("类全名:" + type.FullName);
Console.WriteLine("命名空间名:" + type.Namespace);
Console.WriteLine("程序集名:" + type.Assembly);
Console.WriteLine("模块名:" + type.Module);
Console.WriteLine("基类名:" + type.BaseType);
Console.WriteLine("是否类:" + type.IsClass);
MethodInfo method = type.GetMethod("Show");//获得实例的方法
Console.WriteLine("类的公共成员:");
MemberInfo[] memberInfos = type.GetMembers();//得到所有公共成员
foreach (var item in memberInfos)
{
Console.WriteLine("成员类型:" + item.MemberType + "\t成员:" + item);
}
3.3 示例二:获取公共方法
一:获取所有公共成员
static void Main(string[] args)
{
Type type = typeof(UserInfo);
Console.Write("获取所有公共成员:");
MemberInfo[] members = type.GetMembers();
Console.WriteLine(members.Length);
Console.Write("获取所有公共方法(包含基类):");
MethodInfo[] methods = type.GetMethods();
Console.WriteLine(methods.Length);
Console.Write("获取所有公共构造函数:");
ConstructorInfo[] constructors = type.GetConstructors();
Console.WriteLine(constructors.Length);
Console.Write("获取所有公共字段:");
FieldInfo[] fields = type.GetFields();
Console.WriteLine(fields.Length);
Console.Write("获取所有公共属性:");
PropertyInfo[] properties = type.GetProperties();
Console.WriteLine(properties.Length);
Console.Write("获取所有公共接口:");
Type[] interfaces = type.GetInterfaces();
Console.WriteLine(interfaces.Length);
}
根据名称获取公共成员(不常用)
MemberInfo[] memberInfo1 = type.GetMember("code");
MemberInfo[] memberInfo2 = type.GetMember("Show");
Console.WriteLine(memberInfo1.Length);
Console.WriteLine(memberInfo2.Length);
根据名称获取公共方法
// 获取公共方法(非重载方法)
MethodInfo methodInfo1 = type.GetMethod("Print");
Console.WriteLine(methodInfo1.Name);
// 获取公共重载方法,根据参数顺序,类型,个数获取
// 1.调用有一个int类型参数的重载方法
MethodInfo methodInfo2 = type.GetMethod("Show", new Type[] { typeof(int) });
// 2.调用无参数重载方法(不可传null)
MethodInfo methodInfo3 = type.GetMethod("Show", new Type[0]);
Console.WriteLine(methodInfo3.Name);
根据参数的类型,数量,顺序返回指定构造方法
// 返回无参公共构造方法
ConstructorInfo constructor1 = type.GetConstructor(new Type[0]);
// 返回有一个int类型参数的公共构造方法
ConstructorInfo constructor2 = type.GetConstructor(new Type[] { typeof(int) });
获取类型公共字段
FieldInfo fieldInfo1 = type.GetField("code");
Console.WriteLine(fieldInfo1.Name);
获取类型公共属性
PropertyInfo propertyInfo1 = type.GetProperty("id");
Console.WriteLine(propertyInfo1.Name);
3.4 示例三:获取静态方法
3.5 示例四:获取泛型方法
获取泛型方法
Assembly assembly = typeof(Program).Assembly;
// 获取有一个泛型参数的类
Type type = assembly.GetType("c2.UserInfo`1");
// 指定泛型参数类型
Type generictype = type.MakeGenericType(new Type[] { typeof(int) });
object oType = Activator.CreateInstance(generictype);
4. MethodInfo 方法
一个 MethodInfo
表示一个方法(公共,私有,静态,构造)
4.1 实例属性,方法
实例属性
属性 | 属性值类型 | 描述 |
---|---|---|
methodInfo.Name | string | 方法名称 |
methodInfo.ReturnType | Type | 获取方法返回值类型 |
methodInfo.IsConstructor | bool | 是否是构造方法 |
methodInfo.IsAbstract | bool | 是否为抽象方法 |
methodInfo.IsPrivate | bool | 是否为私有方法 |
methodInfo.IsPublic | bool | 是否为公共方法 |
methodInfo.IsStatic | bool | 是否为静态方法 |
methodInfo.IsVirtual | bool | 是否为虚方法 |
methodInfo.IsGenericMethod | bool | 是否为泛型方法 |
实例方法
方法 | 返回值 | 描述 |
---|---|---|
methodInfo.Invoke(...) | object | 调用非泛型方法 |
methodInfo.GetParameters() | ParameterInfo[] | 获取方法参数数组 |
4.2 操作示例一
获取调用非泛型非重载无参数方法,无参数方法第二个参数可传
null
,但不推荐
Type type = typeof(UserInfo);
object oType = Activator.CreateInstance(type);
MethodInfo methodInfo = null;
methodInfo = type.GetMethod("Print");
methodInfo.Invoke(oType,new object[0]);
methodInfo.Invoke(oType, null);
获取调用非泛型重载无参数方法,无参数方法第二个参数可传
null
,但不推荐
Type type = typeof(UserInfo);
object oType = Activator.CreateInstance(type);
MethodInfo methodInfo = null;
methodInfo = type.GetMethod("Show",new Type[0]);
methodInfo.Invoke(oType,new object[0]);
methodInfo.Invoke(oType, null);
获取调用非泛型重载有参数方法,多个参数用逗号隔开,参数类型,个数,顺序必须相同
Type type = typeof(UserInfo);
object oType = Activator.CreateInstance(type);
MethodInfo methodInfo = null;
methodInfo = type.GetMethod("Show", new Type[] { typeof(int) });
methodInfo.Invoke(oType, new object[] { 1 });
4.3 操作示例二
获取泛型方法与获取普通方法一致,泛型参数按从左到右顺序传入,方法参数类型与泛型参数类型一致
调用公共泛型无参方法
// 获取泛型方法
MethodInfo methodInfo = type.GetMethod("GenericS");
// 指定泛型方法参数
MethodInfo genericmethod = methodInfo.MakeGenericMethod(new Type[] { typeof(int) });
genericmethod.Invoke(oType, null);
调用公共泛型有参方法
MethodInfo methodInfo = type.GetMethod("GenericsA");
MethodInfo genericsmethod =
methodInfo.MakeGenericMethod(new Type[] { typeof(int), typeof(string) });
genericsmethod.Invoke(oType, new object[] { 1 });
调用公共泛型类的有参方法,泛型类中的泛型参数与泛型方法的泛型参数一致
static void Main(string[] args)
{
Assembly assembly = typeof(Program).Assembly;
Type type = assembly.GetType("c2.UserInfo`1");
Type generictype = type.MakeGenericType(new Type[] { typeof(int) });
object oType = Activator.CreateInstance(generictype);
MethodInfo methodInfo = generictype.GetMethod("GenericC");
methodInfo.Invoke(oType, new object[] { 1 });
}
调用公共泛型类的有参方法,泛型类中的泛型参数与泛型方法的泛型参数不一致
static void Main(string[] args)
{
Assembly assembly = typeof(Program).Assembly;
Type type = assembly.GetType("c2.UserInfo`1");
Type generictype = type.MakeGenericType(new Type[] { typeof(int) });
object oType = Activator.CreateInstance(generictype);
MethodInfo methodInfo = generictype.GetMethod("GenericsA");
MethodInfo genericsmethodinfo =
methodInfo.MakeGenericMethod(new Type[] { typeof(string) });
genericsmethodinfo.Invoke(oType, new object[] { 2, "af" });
}
5. ConstructorInfo 构造函数
5.1 实例属性,方法
实例方法
方法 | 返回值类型 | 描述 |
---|---|---|
constructor.Invoke(...) | object | 执行构造函数 |
5.2 操作实例一
public class User
{
public User()
{
Console.WriteLine("无参构造函数");
}
public User(string n)
{
Console.WriteLine("有参构造函数:" + n);
}
}
调用构造函数
static void Main(string[] args)
{
Assembly assembly = typeof(Program).Assembly;
Type type = assembly.GetType("c2.User");
object oType = Activator.CreateInstance(type);
ConstructorInfo constructor = type.GetConstructor(new Type[0]);
constructor.Invoke(oType, null);
ConstructorInfo constructor1 = type.GetConstructor(new Type[] { typeof(string) });
constructor1.Invoke(oType, new object[] { "liai" });
ParameterInfo[] parameters = constructor1.GetParameters();
Console.WriteLine(parameters.Length);
}
6. PropertyInfo 属性
6.1 实例属性,方法
实例属性
属性 | 属性值类型 | 描述 |
---|---|---|
property.Name | string | 获取属性名称 |
property.CanRead | bool | 获取属性是否可读 |
property.CanWrite | bool | 获取属性是否可写 |
property.PropertyType | Type | 获取属性类型 |
实例方法
方法 | 返回值类型 | 描述 |
---|---|---|
property.SetValue(...) | void | 设置对象属性 |
property.GetValue(...) | object | 获取对象属性值 |
6.2 操作实例一
获取公共属性
// 获取所有
PropertyInfo[] propertys = type.GetProperty();
// 获取指定
PropertyInfo property = type.GetProperty("no");
获取属性,设置属性值,获取属性值
class Program
{
static void Main(string[] args)
{
Assembly assembly = typeof(Program).Assembly;
Type type = assembly.GetType("c2.Order");
object oType = Activator.CreateInstance(type);
PropertyInfo property = type.GetProperty("no");
property.SetValue(oType,1);
var value = property.GetValue(oType);
Console.WriteLine(value);
}
}
public class Order
{
public int no { get; set; }
}
7. FieldInfo 字段
7.1 实例属性,方法
实例属性
属性名 | 属性值类型 | 描述 |
---|---|---|
field.Name | string | 获取字段名称 |
field.Is... | bool | 获取字段是否为... |
field.FieldType | Type | 获取字段类型 |
实例方法
7.2 操作实例一
获取字段
class Program
{
static void Main(string[] args)
{
Assembly assembly = typeof(Program).Assembly;
Type type = assembly.GetType("c2.Order");
object oType = Activator.CreateInstance(type);
FieldInfo field = type.GetField("name");
FieldInfo[] fields = type.GetFields();
}
}
public class Order
{
public string name;
}
获取值,设置值
class Program
{
static void Main(string[] args)
{
Assembly assembly = typeof(Program).Assembly;
Type type = assembly.GetType("c2.Order");
object oType = Activator.CreateInstance(type);
FieldInfo field = type.GetField("name");
field.SetValue(oType, "libai");
var value = field.GetValue(oType);
Console.WriteLine(value);
}
}
public class Order
{
public string name;
}
8. 扩展补充
8.1 加载程序集
获得当前【应用程序域】中的所有程序集
Assembly[] ass = AppDomain.CurrentDomain.GetAssemblies();
获得当前对象所属的类所在的程序集
Assembly assembly = this.GetType().Assembly;
Assembly.LoadFile
与Assembly.LoadFrom
的差别
-
LoadFile
方法用来载入和检查具有同样标识但位于不同路径中的程序集,但不会载入程序的依赖项 -
LoadFrom
不能用于载入标识同样但路径不同的程序集
创建实例对象
此方法的作用与 new
一个实例对象相同
Activator.CreateInstance(type)
8.2 Module 程序集模块
Assembly assembly = Assembly.Load("mscorlib");//加载程序集
Module module = assembly.GetModule("CommonLanguageRuntimeLibrary");//得到指定模块
Console.WriteLine("模块名:" + module.Name);
Type[] types = module.FindTypes(Module.FilterTypeName, "Assembly*");
foreach (var item in types)
{
Console.WriteLine("类名:" + item.Name);//输出类型名
}
Console.Read();
8.3 BindingFlags说明
枚举值 | 描述 |
---|---|
BindingFlags.Instance | 对象实例 |
BindingFlags.Static | 静态成员 |
BindingFlags.Public | 指可在搜索中包含公共成员 |
BindingFlags.NonPublic | 指可在搜索中包含非公共成员(即私有成员和受保护的成员) |
BindingFlags.FlattenHierarchy | 指可包含层次结构上的静态成员 |
BindingFlags.IgnoreCase | 表示忽略 name 的大小写 |
BindingFlags.DeclaredOnly | 仅搜索 Type 上声明的成员,而不搜索被简单继承的成员 |
BindingFlags.CreateInstance | 表示调用构造函数。忽略 name。对其他调用标志无效 |
8.4 属性应用:ORM
简易实现
public Order Find(int id)
{
string sql = "select id,name,createTime from order where id = " +id;
Type type = typeof(Order);
object oOrder = Activator.CreateInstance(type);
using (SqlConnection connection = new SqlConnection("constr"))
{
SqlCommand cmd = new SqlCommand(sql,connection);
connection.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
foreach (var prop in type.GetProperties())
{
prop.SetValue(oOrder,prop.Name);
}
}
}
return (Order)oOrder;
}
// DTD
public class Order
{
public int no { get; set; }
public string name { get; set; }
public DateTime createTime { get; set; }
}
泛型版本
public T Find<T>(int id) where T : BaseEntity
{
string sql = "select id,name,createTime from order where id = " + id;
Type type = typeof(T);
object oOrder = Activator.CreateInstance(type);
using (SqlConnection connection = new SqlConnection("constr"))
{
SqlCommand cmd = new SqlCommand(sql, connection);
connection.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
foreach (var prop in type.GetProperties())
{
prop.SetValue(oOrder, prop.Name);
}
}
}
return (T)oOrder;
}
public class BaseEntity { }
public class Order:BaseEntity
{
public int no { get; set; }
public string name { get; set; }
public DateTime createTime { get; set; }
}