簡介
反射是 Microsoft .NET framework 中非常強大的特性,它在 System.Reflection 命名空間中提供了一整套在運行時動態載入/處理程序集的 API。
反射 (Reflection) 如此強大,但仍需謹慎使用,因為它實際上是一種用性能換擴展性的一種方法,這裡提供了一些優化反射的方法,以期把對性能的影響最小化。
場景1(動態調用一個方法)
一個標准且完整的動態調用方法示例
[csharp] public void DynamicExecution()
{
Type objType = Type.GetType("DynamicExecution.Test.DynamicClass");
object obj = Activator.CreateInstance(objType);
MethodInfo mInfo = objType.GetMethod("AddNumbers",
new Type[] { typeof(int), typeof(int) });
// 動態調用 obj 對象上的 AddNumbers 方法
mInfo.Invoke(obj, new object[] { 1, 5 });
PropertyInfo pInfo = objType.GetProperty("TEST_ID");
// 動態設置 obj 對象的 ID 屬性
pInfo.SetValue(obj, "TEST_ID", null );
}
public void DynamicExecution()
{
Type objType = Type.GetType("DynamicExecution.Test.DynamicClass");
object obj = Activator.CreateInstance(objType);
MethodInfo mInfo = objType.GetMethod("AddNumbers",
new Type[] { typeof(int), typeof(int) });
// 動態調用 obj 對象上的 AddNumbers 方法
mInfo.Invoke(obj, new object[] { 1, 5 });
PropertyInfo pInfo = objType.GetProperty("TEST_ID");
// 動態設置 obj 對象的 ID 屬性
pInfo.SetValue(obj, "TEST_ID", null );
} 缺點:
每次動態調用方法(或屬性)將比靜態調用方式慢好幾倍
編譯時方法/屬性的傳入參數的類型安全性無法檢查,在運行時傳入錯誤數據/類型就會掛掉
編碼量大,且可讀性不高
優化代碼
[csharp] Public void OptimizedDynamicExecution()
{
Type objType = Type.GetType("DynamicExecution.Test.DynamicClass");
IDynamicClass obj = Activator.CreateInstance(objType) as IDynamicClass;
if (null != obj)
{
// 靜態調用 obj 對象上的 AddNumbers 方法
int result = obj.AddNumbers(1, 5);
// 靜態設定 obj 對象的 ID 屬性
obj.ID = 10;
}
}
// 在 OptimizedDynamicExecution API 中使用的 Interface
public interface IDynamicClass
{
int ID { get; set; }
int AddNumbers(int a, int b);
}
Public void OptimizedDynamicExecution()
{
Type objType = Type.GetType("DynamicExecution.Test.DynamicClass");
IDynamicClass obj = Activator.CreateInstance(objType) as IDynamicClass;
if (null != obj)
{
// 靜態調用 obj 對象上的 AddNumbers 方法
int result = obj.AddNumbers(1, 5);
// 靜態設定 obj 對象的 ID 屬性
obj.ID = 10;
}
}
// 在 OptimizedDynamicExecution API 中使用的 Interface
public interface IDynamicClass
{
int ID { get; set; }
int AddNumbers(int a, int b);
} 改進點
靜態調用提高了執行速度
引入一個 interface 實現了類型安全
該 interface 能在其他場景使用
更短的代碼,更好的可讀性
場景2(讀取自定義屬性custom attributes)
標准代碼示例
[csharp] [Table(Name="Employees")]
public class Employee : Entity
{
[PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public DateTime DOB { get; set; }
}
public class Entity
{
public Entity()
{
Type curType = this.GetType();
object[] tableAttributes = curType.GetCustomAttributes(typeof(TableAttribute), true);
if(null != tableAttributes && tableAttributes.Count() > 0)
{
// Retrieve the attribute information
}
// Use the attribute information here
}
}
[Table(Name="Employees")]
public class Employee : Entity
{
[PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public DateTime DOB { get; set; }
}
public class Entity
{
public Entity()
{
Type curType = this.GetType();
object[] tableAttributes = curType.GetCustomAttributes(typeof(TableAttribute), true);
if(null != tableAttributes && tableAttributes.Count() > 0)
{
// Retrieve the attribute information
}
// Use the attribute information here
}
}以上定義了一個 Employee 實體類,對應數據庫中的表明為 “Employees”,另外它的 Id 屬性標記了一個 PrimaryKey 屬性。
為了獲取這些定義在 custom attributes 中的信息,在父類中使用了反射。
缺點
每次實例化都通過反射來獲取 custom attributes 信息
通過反射來獲取 custom attributes 信息是個“重活”
優化代碼
[csharp] // 追加一個 C# 結構來存儲信息
public struct TableInfo
{
public string TableName;
public string PrimaryKey;
}
public class Entity
{
private static Dictionary<Type, TableInfo> tableInfoList = new Dictionary<Type, TableInfo>();
public Entity()
{
Type curType = this.GetType();
TableInfo curTableInfo;
if (!tableInfoList.TryGetValue(curType, out curTableInfo))
{
lock (this)
{
// 使用雙重檢查來降低 lock 消耗
if (!tableInfoList.TryGetValue(curType, out curTableInfo))
{
object[] tableAttributes = curType.GetCustomAttributes(typeof(TableAttribute), true);
if(null != tableAttributes && tableAttributes.Count() > 0)
{
// 緩存中沒有時,新建一個 TableInfo
curTableInfo = new TableInfo();
curTableInfo.TableName = ((TableAttribute) tableAttributes[0]).Name;
// 其他處理
//把新建的 TableInfo 加入緩存
tableInfoList.Add(curType, curTableInfo);
}
}
}
}
// use curTableInfo here
}
}
// 追加一個 C# 結構來存儲信息
public struct TableInfo
{
public string TableName;
public string PrimaryKey;
}
public class Entity
{
private static Dictionary<Type, TableInfo> tableInfoList = new Dictionary<Type, TableInfo>();
public Entity()
{
Type curType = this.GetType();
TableInfo curTableInfo;
if (!tableInfoList.TryGetValue(curType, out curTableInfo))
{
lock (this)
{
// 使用雙重檢查來降低 lock 消耗
if (!tableInfoList.TryGetValue(curType, out curTableInfo))
{
object[] tableAttributes = curType.GetCustomAttributes(typeof(TableAttribute), true);
if(null != tableAttributes && tableAttributes.Count() > 0)
{
// 緩存中沒有時,新建一個 TableInfo
curTableInfo = new TableInfo();
curTableInfo.TableName = ((TableAttribute) tableAttributes[0]).Name;
// 其他處理
//把新建的 TableInfo 加入緩存
tableInfoList.Add(curType, curTableInfo);
}
}
}
}
// use curTableInfo here
}
}這裡沒有對反射操作進行修改,而是通過對反射的信息進行緩存從而減少反射操作的次數來提高程序的執行效率
優點
每種類型僅在初次使用時需要反射
通過緩存最小化反射的使用次數
場景3(依賴注入 Dependency Injection)
如今的程序都強調低耦合,允許在運行時動態載入配置文件中指定的程序集/模塊,這通常都使用到反射並因此降低了運行效率。
Microsoft .NET 輕量級代碼生成可以解決“創建設計時未知類型對象”的問題,它在 System.Reflection.Emit 命名空間下提供了多個 API。
這個技術能減少反射的過度使用,並且通過緩存方法代理的方式來提高後續調用的執行效率。
[csharp] // 依賴的協議接口
public interface ISecurityProvider
{
bool ValidateUser(string userId, string password);
List<User> GetUsersList();
}
// 依賴的具體實現類
public class DefaultSecurityProvider : ISecurityProvider
{
public bool ValidateUser(string userId, string password)
{
...
}
public List<User> GetUsersIist()
{
...
}
}
// 動態實例化 DefaultSecuirtyProvider 的方法
private void CreateInstance()
{
Type classType = Type.GetType("DefaultSecurityProvider, AssemblyName");
// 使用反射動態創建一個實例對象
ISecurityProvider employeeInstance =
Activator.CreateInstance(classType) as ISecurityProvider;
}
// 依賴的協議接口
public interface ISecurityProvider
{
bool ValidateUser(string userId, string password);
List<User> GetUsersList();
}
// 依賴的具體實現類
public class DefaultSecurityProvider : ISecurityProvider
{
public bool ValidateUser(string userId, string password)
{
...
}
public List<User> GetUsersIist()
{
...
}
}
// 動態實例化 DefaultSecuirtyProvider 的方法
private void CreateInstance()
{
Type classType = Type.GetType("DefaultSecurityProvider, AssemblyName");
// 使用反射動態創建一個實例對象
ISecurityProvider employeeInstance =
Activator.CreateInstance(classType) as ISecurityProvider;
}缺點
每次都使用反射來創建實例對象
通過反射來獲取 custom attributes 信息是個“重活”
優化代碼
[csharp] view plaincopyprint?// 動態創建實例(不使用反射)
private void CreateInstance()
{
Type classType = Type.GetType("DefaultSecurityProvider, AssemblyName");
// 獲取一個指定對象的實例化方法的代理 delegate
CreateInstanceDelegate createInstance = ObjectInstantiater(classType);
// 執行該代理來獲取指定對象的一個實例
ISecurityProvider employeeInstance = createInstance() as ISecurityProvider;
}
// 動態創建實例(不使用反射)
private void CreateInstance()
{
Type classType = Type.GetType("DefaultSecurityProvider, AssemblyName");
// 獲取一個指定對象的實例化方法的代理 delegate
CreateInstanceDelegate createInstance = ObjectInstantiater(classType);
// 執行該代理來獲取指定對象的一個實例
ISecurityProvider employeeInstance = createInstance() as ISecurityProvider;
}如果你想知道的更多一些,可以參考以下代碼(截取自 System.Reflection.Emit 命名空間)
[csharp] // 實例方法的代理聲明
public delegate object CreateInstanceDelegate();
// 緩存實例方法代理的 Dictionary
private static Dictionary<Type, CreateInstanceDelegate> _createInstanceDelegateList =
new Dictionary<Type, CreateInstanceDelegate>();
// 動態獲取實例化指定類型的方法
public static CreateInstanceDelegate ObjectInstantiater(Type objectType)
{
CreateInstanceDelegate createInstanceDelegate;
if (!_createInstanceDelegateList.TryGetValue(objectType,
out createInstanceDelegate))
{
lock (objectType)
{
if (!_createInstanceDelegateList.TryGetValue(objectType, createInstanceDelegate))
{
// 建立一個新方法
DynamicMethod dynamicMethod =
new DynamicMethod("Create_" + objectType.Name, objectType, new Type[0]);
// 獲取默認的構造函數
ConstructorInfo ctor = objectType.GetConstructor(new Type[0]);
// 生成 IL 代碼
ILGenerator ilgen = dynamicMethod.GetILGenerator();
ilgen.Emit(OpCodes.Newobj, ctor);
ilgen.Emit(OpCodes.Ret);
// 建立一個代理並緩存在 dictionary 中
createInstanceDelegate = (CreateInstanceDelegate)dynamicMethod
.CreateDelegate(typeof(CreateInstanceDelegate));
_createInstanceDelegateList[objectType] = createInstanceDelegate;
}
}
}
return createInstanceDelegate; // 返回對象的實例化代理
}
// 實例方法的代理聲明
public delegate object CreateInstanceDelegate();
// 緩存實例方法代理的 Dictionary
private static Dictionary<Type, CreateInstanceDelegate> _createInstanceDelegateList =
new Dictionary<Type, CreateInstanceDelegate>();
// 動態獲取實例化指定類型的方法
public static CreateInstanceDelegate ObjectInstantiater(Type objectType)
{
CreateInstanceDelegate createInstanceDelegate;
if (!_createInstanceDelegateList.TryGetValue(objectType,
out createInstanceDelegate))
{
lock (objectType)
{
if (!_createInstanceDelegateList.TryGetValue(objectType, createInstanceDelegate))
{
// 建立一個新方法
DynamicMethod dynamicMethod =
new DynamicMethod("Create_" + objectType.Name, objectType, new Type[0]);
// 獲取默認的構造函數
ConstructorInfo ctor = objectType.GetConstructor(new Type[0]);
// 生成 IL 代碼
ILGenerator ilgen = dynamicMethod.GetILGenerator();
ilgen.Emit(OpCodes.Newobj, ctor);
ilgen.Emit(OpCodes.Ret);
// 建立一個代理並緩存在 dictionary 中
createInstanceDelegate = (CreateInstanceDelegate)dynamicMethod
.CreateDelegate(typeof(CreateInstanceDelegate));
_createInstanceDelegateList[objectType] = createInstanceDelegate;
}
}
}
return createInstanceDelegate; // 返回對象的實例化代理
}優點
類對象都通過靜態方法建立
這個技術避免了用反射來進行類實例化
場景4(動態設定 ORM 實體對象的屬性值)
絕大多數的 ORM Frameworks 中,實體類對應了數據庫中的表,並通過反射來讀取/設定實體類的各個屬性,這非常影響性能。這可以借鑒場景3的方法該改善,此處我們將使用.NET Frameworks 3.5 中包含的 Expression Trees 特性來實現。
標准代碼
[csharp] // Employees 實體類
public class Employees
{
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime BirthDate { get; set; }
}
// Employees 列表,用來存放從 data reader 讀取到的所有數據
List<Employees> empList = new List<Employees>();
using(SqlConnection con = new SqlConnection(@"......"))
{
SqlCommand cmd = new SqlCommand("Select * from employees");
cmd.Connection = con;
con.Open();
// 調用 ReadList 把 data reader 查詢出的信息存入實體類
using (SqlDataReader reader = cmd.ExecuteReader())
{
empList = ReadList<Employees>(reader);
}
}
// 把 data reader 查詢出的信息轉換成實體列表
public List<T> ReadList<T>(SqlDataReader reader) where T : new()
{
var list = new List<T>();
while (reader.Read())
{
T entity = new T();
Type entityType = typeof(T);
foreach(var entityProperty in entityType.GetProperties())
{
// 使用反射來設定實體對象的每一個屬性
entityProperty.SetValue(entity, reader[entityProperty.Name], null);
} list.Add(entity);
}
return list;
}
// Employees 實體類
public class Employees
{
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime BirthDate { get; set; }
}
// Employees 列表,用來存放從 data reader 讀取到的所有數據
List<Employees> empList = new List<Employees>();
using(SqlConnection con = new SqlConnection(@"......"))
{
SqlCommand cmd = new SqlCommand("Select * from employees");
cmd.Connection = con;
con.Open();
// 調用 ReadList 把 data reader 查詢出的信息存入實體類
using (SqlDataReader reader = cmd.ExecuteReader())
{
empList = ReadList<Employees>(reader);
}
}
// 把 data reader 查詢出的信息轉換成實體列表
public List<T> ReadList<T>(SqlDataReader reader) where T : new()
{
var list = new List<T>();
while (reader.Read())
{
T entity = new T();
Type entityType = typeof(T);
foreach(var entityProperty in entityType.GetProperties())
{
// 使用反射來設定實體對象的每一個屬性
entityProperty.SetValue(entity, reader[entityProperty.Name], null);
} list.Add(entity);
}
return list;
}缺點
通過反射來設定屬性值是個“重活”
開發者需要自行確保每個屬性的類型安全
優化代碼
[csharp] public List<T> ReadList<T>(SqlDataReader reader)
{
var list = new List<T>();
Func<SqlDataReader, T> readRow = GetReader<T>();
// 從 GetReader 函數獲取一個代理,並通過該代理來創建對象
while (reader.Read())
{
list.Add(readRow(reader));
}
return list;
}
// 使用一個 ConcurrentDictionary 來緩存各類型的代理
ConcurrentDictionary<Type, Delegate> ExpressionCache = new ConcurrentDictionary<Type, Delegate>();
// 獲取(並創建緩存)各類型的初始化 Expression Trees
// PS:原文未提供該函數,所以翻譯的時候自己寫了下(簡單調試通過),如有問題請聯系我
public Func<SqlDataReader, T> GetReader<T>()
{
Delegate func;
var entityType = typeof(T);
if (!ExpressionCache.TryGetValue(entityType, out func))
{
// lambda 輸入參數
var readerParam = Expression.Parameter(typeof(SqlDataReader), "reader");
// lambda 處理
var assignmentExpressions = new List<MemberBinding>();
foreach (var entityProperty in entityType.GetProperties())
{
var assignmentExpression = Expression.Bind(
entityProperty.GetSetMethod(),
Expression.Convert(
Expression.Call(
readerParam,
typeof(SqlDataReader).GetMethod("get_Item", new[] { typeof(string) }),
Expression.Constant(entityProperty.Name, typeof(string))),
entityProperty.PropertyType));
assignmentExpressions.Add(assignmentExpression);
}
var bodyExporess = Expression.MemberInit(Expression.New(entityType), assignmentExpressions);
var lambda = Expression.Lambda<Func<SqlDataReader, T>>(bodyExporess, new[] { readerParam });
func = lambda.Compile();
ExpressionCache.TryAdd(entityType, func);
}
return (Func<SqlDataReader, T>)func;
}
public List<T> ReadList<T>(SqlDataReader reader)
{
var list = new List<T>();
Func<SqlDataReader, T> readRow = GetReader<T>();
// 從 GetReader 函數獲取一個代理,並通過該代理來創建對象
while (reader.Read())
{
list.Add(readRow(reader));
}
return list;
}
// 使用一個 ConcurrentDictionary 來緩存各類型的代理
ConcurrentDictionary<Type, Delegate> ExpressionCache = new ConcurrentDictionary<Type, Delegate>();
// 獲取(並創建緩存)各類型的初始化 Expression Trees
// PS:原文未提供該函數,所以翻譯的時候自己寫了下(簡單調試通過),如有問題請聯系我
public Func<SqlDataReader, T> GetReader<T>()
{
Delegate func;
var entityType = typeof(T);
if (!ExpressionCache.TryGetValue(entityType, out func))
{
// lambda 輸入參數
var readerParam = Expression.Parameter(typeof(SqlDataReader), "reader");
// lambda 處理
var assignmentExpressions = new List<MemberBinding>();
foreach (var entityProperty in entityType.GetProperties())
{
var assignmentExpression = Expression.Bind(
entityProperty.GetSetMethod(),
Expression.Convert(
Expression.Call(
readerParam,
typeof(SqlDataReader).GetMethod("get_Item", new[] { typeof(string) }),
Expression.Constant(entityProperty.Name, typeof(string))),
entityProperty.PropertyType));
assignmentExpressions.Add(assignmentExpression);
}
var bodyExporess = Expression.MemberInit(Expression.New(entityType), assignmentExpressions);
var lambda = Expression.Lambda<Func<SqlDataReader, T>>(bodyExporess, new[] { readerParam });
func = lambda.Compile();
ExpressionCache.TryAdd(entityType, func);
}
return (Func<SqlDataReader, T>)func;
}優點
開發者不需要為實體類寫賦值代碼
建立的 expression tree 可被多個進程共享
建立 expression tree 相比寫 IL 代碼來說更簡單