本文是学习特性与反射的学习笔记,在介绍完特性和反射之后,会使用特性与反射实现一个简单的将DataTable转换为List的功能,水平有限,如有错误,还请大神不吝赐教。
1. 反射:什么是反射?反射就是在程序运行的过程中,动态的获取类的成员,并对他们进行操作。包括动态调用方法,动态获取,设置属性等。通过特性,也能是想IOC,AOP等功能。
2. 特性:特性只有在使用反射的时候才能发挥它最大的作用,通过反射获取到自定义的特性,再根据特性进行操作,例如在通过反射实现ORM的时候,如果一个熟悉设置了不需要ORM的特性,则可以忽略该属性。如果没有反射,那么可以把特性当做注释,它不会对代码的运行造成任何影响。但它和注释的区别在于,它会被编译进程序集,这样才能通过反射获取到这些特性。
3. 下面先演示反射的用法,反射常用的类有Assembly,Activator,Type这几个类
Assembly获取程序集应用,可以通过Load,LoadFile,LoadFrom这几个方法将dll文件加载进当前程序集。如果需要反射的类位于当前程序集,则可以不使用此类
Activator用于动态的创建一个类的实例,通过Assembly加载的程序集,可以获取到它内部的所有的Type,而Activator.Crea teInstance方法可以为当前对象创建一个实例。(Assembly也有CreateInstance方法用于创建类的实例)。
Type表示一个类型,也可以通过typeof获取例如typeof(int)得到的就是int类型,然后Type对象有很多的方法可以获取对象的成员,包括字段,方法,属性等等对象内部的所有都可以通过Type获得。
下面是使用反射的示例
3.1、 添加一个新的类库,添加下面的代码,并生成为一个dll文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RefleatorDll
{
/// <summary>
/// 此类用于反射获取到的对象的测试类
/// 通过反射获取的类,不需要是public的,即使是private的类也是可以获取到的
/// 类内部的所有成员,即使是private的,全部可以通过反射进行更改,但强烈不建议那样做,私有成员之所以为私有的,一定有必要的原因,
/// 对私有成员的修改可能对类的运行造成难以预料的影响
/// </summary>
public class CustomClass
{
//用于反射的字段,包括私有,公有,静态字段用于演示不同的字段如何修改
//下方的属性相同
private string _name = null;
public int _age = 0;
public static string _address = null;//静态字段不设置属性,如有必要也可以设置
public string Name { get { return _name; } set { _name = value; } }
public int Age { get { return _age; } set { _age = value; } }
//用于反射获取和调用类的方法
//只包括静态和公有方法私有方法的获取请参考私有字段的获取
//方法也包括有返回值与如返回值
//其他情况的方法,请举一反三去获取
public string GetName()
{
return _name;
}
public static void ShowAge(string name,int age)
{
Console.WriteLine($"{name}'s age is :" + age);
}
}
}
3.2、 在主程序中通过反射对此类进行操作
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RefleatorDll.dll");
Assembly asm = Assembly.LoadFile(path);//加载程序集
//asm.GetType方法与GetTypes的区别在于,前者通过指点类型的名称获取指定的Type,后者会获取到当前程序集中的所有类
//类型名使用FullName
Type cs = asm.GetType("RefleatorDll.CustomClass");
//为当前Type创建一个实例
//此方法有很多的重载,可以结合实际情况选择使用哪一个重载
object obj = Activator.CreateInstance(cs);
//获取及修改私有的字段
//如果是通过New的到的对象,私有字段是不能获取及修改的,但是通过反射就可以,同理属性,方法也是一样
//BindingFlags用于设置指定类型的字段,包括私有的,公有的,静态的,继承的,非继承的,等类型
//下面的方法只获取公有的,静态的,非公有的,如果不知道则默认获取公有字段
FieldInfo[] fis = cs.GetFields(BindingFlags.Static|BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance);
FieldInfo fi = cs.GetField("_name", BindingFlags.Instance | BindingFlags.NonPublic);
//如果是实例字段,则需要将上面创建的obj传入,如果是静态字段则传入null即可
Console.WriteLine(fi.GetValue(obj).ToString());//显示当前值,当前显示为""
fi.SetValue(obj, "此坑已满");
Console.WriteLine(fi.GetValue(obj).ToString());//显示当前值,显示为设置的值
Console.WriteLine("方法执行完毕!");
Console.ReadKey();
显示结果为:
属性的操作与字段操作相同只是将FieldInfo换为PropertyInfo即可
下面演示方法的调用
创建对象的方法上面的实例已经有了,所以此处只附调用方法的代码
//带返回值的方法
//如果是重载方法的话,需要在参数2中指定需要使用的方法的参数对应的数量和类型
MethodInfo mis = cs.GetMethod("GetName", BindingFlags.Instance|BindingFlags.Public);
//参数2指定传入的参数数组如果当前执行的方法没有参数则传入null即可
object returnValue = mis.Invoke(obj, null);
Console.WriteLine(returnValue);//由于上面设置了_name的值,所以此处显示设置的值
//静态方法
MethodInfo misS = cs.GetMethod("ShowAge", BindingFlags.Static | BindingFlags.Public);
misS.Invoke(null, new object[] { "此坑已满", 26 });
执行结果
以上就是反射的简单使用方法,下面是反射结合特性的方法
1. 定义一个自定义的特性,.Net框架自带很多的特性,如果写过WCF或者MVC就会有深刻的体验,比[Required],[HttpGet]等
//AttributeUsage特性用于自定义特性,它用于设置特性的作用范围
//例如此特性就只能用在方法上,已经特性是否可以多次使用,是否继承父类的特性等
//自定义特性约定为{name}+Attribute,当然也可以不加Attribute
//区别在于加后在使用时可以不加Attribute否则就必须输入特性的全名
//例如ShowAttribute使用时只需要使用[Show]即可
//自定义特性必须继承自Attribute类
[AttributeUsage(AttributeTargets.Method)]
public class ShowAttribute : Attribute
{
public ShowAttribute(string methodName)
{
Console.WriteLine("您通过特性找到了方法:" + methodName);
}
}
2.定义一个类并添加此特性
//此类用于演示ShowAttribute特性
public class ShowAttTest
{
[Show("ShowMethod")]
public void ShowMethod()
{
//因为此方法只是用来测试特性的功能的,所以不做具体业务处理,直接返回
return;
}
}
3.通过反射调用此方法
//反射特性测试,用于测试静态方法获取自定义特性与使用实例方法获取自定义特性是否执行特性内部方法的差别
Type t = typeof(ShowAttTest);
MethodInfo mi = t.GetMethod("ShowMethod");
//通过反射的实例方法获取自定义特性,此方法会造成特性的代码被执行
mi.GetCustomAttribute(typeof(ShowAttribute));
//通过静态方法获取自定义特性,此方法不会执行特性的代码
//CustomAttributeData.GetCustomAttributes(mi);
运行结果:
以上就是反射和特性的基础知识了,下面使用反射和特性实现一个简单的
将DataTable转换为实体类
获取到的DataTable的数据
转换后的数据请自行调试
示例源码
1. 自定义的特性
//此特性仅用于忽略字段标识,同时设置为只能为属性添加
[AttributeUsage(AttributeTargets.Property)]
public class IngoreAttribute:Attribute
{
}
//此特性用于设置当前属性对应的数据库字段的名称
[AttributeUsage(AttributeTargets.Property)]
public class ColumnNameAttribute : Attribute
{
public string Name;//用于保存字段的名称
public ColumnNameAttribute(string name)
{
Name = name;
}
}
2. 自定义实体
//此类模拟一个实体类
public class CustomEntity
{
public int ID { get; set; }
public string Name { get; set; }
[ColumnName("TelePhone")]//设置Phone的字段为TelePhone
public string Phone { get; set; }
public string Email { get; set; }
[Ingore]//忽略此字段,不从DataTable中获取
public string IngoreTest { get; set; }
}
3. 转换类
//此类用于转换DataTable到List
public class ConvertToList
{
/// <summary>
/// 实际转换的方法--约束泛型参数T只能是应用类型,同时必须包含一个无参构造函数
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public List<T> GetList<T>(DataTable dataTable) where T : class, new()
{
List<T> list = new List<T>();
Type type = typeof(T);
//获取当前的实体的所有公共属性
PropertyInfo[] pis = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach(DataRow row in dataTable.Rows)
{
T t = new T();//创建一个实例
foreach (var item in pis)
{
//如果属性设置了Ingore特性,则直接跳过此属性
Attribute ingore = item.GetCustomAttribute(typeof(IngoreAttribute));
if (ingore != null)
{
continue;
}
string fieldName = item.Name;//取出属性的默认名字当做字段默认名
ColumnNameAttribute columnName = item.GetCustomAttribute(typeof(ColumnNameAttribute)) as ColumnNameAttribute;
//如果当前属性添加了ColumnName特性,则设置字段名为Name
if (columnName != null)
{
fieldName = columnName.Name;
}
//由于演示的关系此处只添加了int类型的转换
if (item.PropertyType == typeof(int))
{
item.SetValue(t, Convert.ToInt16(row[fieldName].ToString()));//为当前属性赋值*注意此处可能存在装箱拆箱的问题
}
else
{
item.SetValue(t, row[fieldName]);
}
}
list.Add(t);
}
return list;
}
}
4. 测试代码
//ConvertToList测试
//此处模拟从数据库获取的数据,实际项目中请从真实的数据库获取数据
DataTable dt = new DataTable();
dt.Columns.Add("ID");
dt.Columns.Add("Name");
dt.Columns.Add("TelePhone");
dt.Columns.Add("Email");
dt.Columns.Add("IngoreTest");//此字段为忽略字段,列添加此字段只是为了演示转换过程中确实会忽略该字段
DataRow dr1 = dt.NewRow();
dr1["ID"] = 1;
dr1["Name"] = "张三";
dr1["TelePhone"] = "123456";
dr1["Email"] = "test@test.com";
dr1["IngoreTest"] = "Ingore";
dt.Rows.Add(dr1);
DataRow dr2 = dt.NewRow();
dr2["ID"] = 2;
dr2["Name"] = "李四";
dr2["TelePhone"] = "456789";
dr2["Email"] = "qwer@test.com";
dr2["IngoreTest"] = "IngoreTest";
dt.Rows.Add(dr2);
ConvertToList ct = new ConvertToList();
List<CustomEntity> customs = ct.GetList<CustomEntity>(dt);
以上就是通过反射和特性实现转换的功能,更多用法请参考相关教程,谢谢!