本文是學習特性與反射的學習筆記,在介紹完特性和反射之后,會使用特性與反射實現一個簡單的將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);
以上就是通過反射和特性實現轉換的功能,更多用法請參考相關教程,謝謝!