[知识译栈] .Net 反射优化

反射优化技术

原文链接:www.codeproject.com/Articles/503527/Reflection-optimization-techniques

这篇文章是关于.Net的反射优化(有效率地使用反射)的技术。

文中有翻译不准确、难以理解的内容可在评论出指点或咨询。

概述

反射(Reflection)是一种非常强大的.Net平台的特性。System.Reflection命名空间下提供了一套丰富的加载和调用程序集和对象的接口,除此之外它还提供了 了运行时动态地检索元数据信息的方法,比如获取属性、字段信息,特性信息等。

使用反射确实可以使开发人员的生活变得轻松,但是应该尽可能少地使用它,换句话说,只在必需的情况下使用它。它在很大程度上影响了程序的性能。以下是几种提升反射效率的方法。

大多数时候我们使用反射而没有意识到它带来的成本。在本文章中我们将介绍一些关于提升反射效率的技术。

优化方案

本文我们将讨论协一些彻底避免使用反射或至少避免重复地调用反射。在本文章剩余的部分中我们将介绍在什么情景下可以有效地使用反射。

情景一:动态调用方法

如果一个运行时创建的对象的方法/成员的名称在开发时是已知的,那么就可以使用接口静态地调用避免使用反射动态地调用。

代码案例:动态调用

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) });

    // Executing AddNumbers method dynamically on obj
    mInfo.Invoke(obj, new object[] { 1, 5 });

    PropertyInfo pInfo = objType.GetProperty("ID");

    // Set property ID dynamically on obj
    pInfo.SetValue(obj, "TEST_ID", null );

} 

以上代码的缺点

  1. 每次动态调用都会比静态调用同样的方法和属性耗费数倍甚至更长的时间;
  2. 方法参数类型和属性值类型安全在编译时没有被检查,在运行时如果参数提供不正确的参数类型可能会导致代码崩溃。
  3. 代码比优化过的代码更长,并且以后更难维护。

代码案例:优化代码

Public void OptimizedDynamicExecution()
{
    Type objType = Type.GetType("DynamicExecution.Test.DynamicClass");

    IDynamicClass obj = Activator.CreateInstance(objType) as IDynamicClass;

    if (null != obj)
    {
        // Executing AddNumbers method statically on obj
        int result = obj.AddNumbers(1, 5);

        // Set property ID statically on obj
	// The code will not compile if value is assigned as "TEST_ID"
        obj.ID = 10; 
    }
}

// Interface to be consumed by OptimizedDynamicExecution API
public interface IDynamicClass
{
    int ID { get; set; }

    int AddNumbers(int a, int b);
} 

以上代码的优点

  1. 当方法被静态地调用时拥有更快的执行速度;
  2. 通过使用接口使得类型安全;
  3. 同一个接口在其它相似的地方使用;
  4. 更短的方法调用代码。

情景二:从一个类型读取自定义属性

自定义属性被用来给一个程序集、一个类型、或者一个类型的成员提供额外的信息。每当创建一个标记有特性的类型实例时它都是必需要读取的,使用一个静态变量去缓存是一个明智的方法,这样只需要在第一次使用时调用反射并且能够在创造后续的实例时避免。

以下是一个实体类的案例

[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; }

}

上述代码是Employee实体类的例子,它与数据库中的"Employees"表保持一致,如果你仔细看的话,属性Id被PrimaryKey特性标记。所有成员包括被属性修饰的类本身都有一些特殊的含义,这是这个类的使用者用来处理实例时所需要的。

我们必须要应用反射读取这个类的特性并按顺序检索信息。如果每个该类型的实例都使用反射获取信息将非常耗费时间。从性能的角度看,一个更好的主意是,仅在第一次使用时用反射读取它并将它缓存,为后续所有实例使用。

代码案例:每个实例都动态检索特性信息

public class Entity
{
    public Entity()
    {
        Type curType = this.GetType();

        // The following reflection code will be executed every time 
        // an instance of this class is created.
        object[] tableAttributes =
		curType.GetCustomAttributes(typeof(TableAttribute), true);

        if(null != tableAttributes && tableAttributes.Count() > 0)
        {
            // Retrieve the attribute information
        }

        // Use the attribute information here 
    }
}

上述代码的缺点

  1. 每当创建该类型的实例时,都要动态地从特性中检索信息;
  2. 使用反射检索特性信息是一种非常昂贵(耗费性能)的操作。

以下案例将论证一个观念,缓存特性信息以实现更好的性能

// C# structure to hold table information
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)
            {
                // double check to ensure that an instance is not 
                // created before this lock
                if (!tableInfoList.TryGetValue(curType, out curTableInfo))
                {
                    object[] tableAttributes =
			curType.GetCustomAttributes(typeof(TableAttribute), true);

                    if(null != tableAttributes && tableAttributes.Count() > 0)
                    {
                        curTableInfo = new TableInfo();
                        curTableInfo.TableName = ((TableAttribute) tableAttributes[0]).Name;
                    }
                }
            }
        }
    }
}

上述代码示范了如何一次性检索特性信息并供相同类型的后续实例使用,而不必每次都执行反射。

上述技术的优点

  1. 反射只在第一次时使用,后续所有时间都使用已经缓存副本信息;
  2. 通过使用缓存技术将反射成本降到最低。

情景三:依赖注入

通常情况下,我们需要在一些代码中注入依赖关系来解耦一些特定的逻辑与用户逻辑,因此在以后的时间中可以由不同的提供者提供逻辑实现。为执行此情景,中我们通常允许用户通过配置文件来配置程序集和类型。

在这种场景中,应用将需要加载程序集并每次都使用反射动态创建所依赖类型的实例。这种操作相对处理逻辑是非常昂贵,并且在应用程序这一部分大量流量可能会导致性能不佳。

微软.Net 轻量级代码生成帮助解决了创建一个在设计时并不知道的对象实例的问题。在System.Reflection.Emit命名空间下它提供了一组API通过编程的方式创建程序集、类型和方法等。

这种技术将静态地创建所需类型的实例,因此可以减少每次使用反射动态创建实例带来的开销。

代码案例:论证以上观念

// Contract for the dependency class
public interface ISecurityProvider
{
    bool ValidateUser(string userId, string password);
    List<User> GetUsersList();
}

// Actual implementation of the dependency class to be instantiated
public interface DefaultSecurityProvider : ISecurityProvider
{
    public bool ValidateUser(string userId, string password)
    {
        ...
    }
    public List<User> GetUsersIist()
    {
        ...
    }
}

代码案例:完全动态调用

// Method that needs to create the instance of DefaultSecuirtyProvider
// class dynamically
private void CreateInstance()
{
    Type classType =
        Type.GetType("DefaultSecurityProvider, AssemblyName");
    
    // Follwoing code creates the instacne of dependency class
    // dynamically using reflection every time.
    ISecurityProvider employeeInstance = Activator.CreateInstance(classType) as ISecurityProvider;
} 

以下的委托将用来映射(保存)微软.Net轻量级代码生成技术动态创建的方法

// Delegate for holding object instantiator method
public delegate object CreateInstanceDelegate(); 

以下的字典对象将用来保存为实例化依赖对象而动态创建的方法的委托

// Function that creates the method dynamically for creating the instance
// of a given class type
public static CreateInstanceDelegate ObjectInstantiater(Type objectType)
{
    CreateInstanceDelegate createInstanceDelegate;

    if (!_createInstanceDelegateList.TryGetValue(objectType, 
        out createInstanceDelegate))
    {
        lock (objectType)
        {
            if (!_createInstanceDelegateList.TryGetValue(objectType, 
         out createInstanceDelegate))
            {
                // Create a new method.        
                DynamicMethod dynamicMethod =
                    new DynamicMethod("Create_" + objectType.Name,
	           objectType, new Type[0]);

                // Get the default constructor of the plugin type
                ConstructorInfo ctor = objectType.GetConstructor(new Type[0]);

                // Generate the intermediate language.       
                ILGenerator ilgen = dynamicMethod.GetILGenerator();
                ilgen.Emit(OpCodes.Newobj, ctor);
                ilgen.Emit(OpCodes.Ret);

                // Create new delegate and store it in the dictionary
                createInstanceDelegate = (CreateInstanceDelegate)dynamicMethod
                    .CreateDelegate(typeof(CreateInstanceDelegate));
                _createInstanceDelegateList[objectType] = createInstanceDelegate;
            }
        }
    }
    return createInstanceDelegate; // return the object instantiator delegate
}  

上述代码论证了怎样静态地创建在设计时不知道的任何类型的对象,并只通过它的接口约束创建。

以上案例中我们使用System.Reflection.Emit命名空间下的API动态地创建了一个代理方法,它可以静态地创建所依赖类型的实例。第一次使用时我们会创建并使用所依赖类型做key存储到字典中,后续使用时即可从字典取出委托并使用。

该技术的优点

  1. 静态地创建类型的实例使用对象的实例化方法;
  2. 该技术避免使用反射来创建对象实例。

情景四:动态设置ORM实体的属性

在所有ORM框架中,实体类型映射到数据库表。框架提供反射读取属性值以及设置属性值为实体类型的每一个实例。该操作非常昂贵并有可能显著地降低程序性能。可以通过使用情景3中所论证的方法,即通过生成动态方法来实现,但是在这个例子中我们将使用一个.Net Framework 3.5提供的叫做表达式树的新特性来实现。

代码案例论证这个观点

// Entity class 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 list to be populated with data retrieved from the data reader
List<Employees> empList = new List<Employees>(); 

代码案例:通过使用DataReader填充Employees列表

// Employees list to be populated with data retrieved from the data reader
List<Employees> empList = new List<Employees>();

using(SqlConnection con = new SqlConnection(@"Data Source=localhost\SQLEXPRESS;
   Initial Catalog=Northwind;Integrated Security=True"))
{
    SqlCommand cmd = new SqlCommand("Select * from employees");
    cmd.Connection = con;
    con.Open();

    // Call the ReadList method seding Employees as the type of entity
    // and the data reader as the parameter
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        empList = ReadList<Employees>(reader);
    }
}

代码案例:使用反射动态地为实体类型的属性赋值

// Method to be called for getting the list of entity class
// filled with data from the 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())
        {
            // Using reflection for setting the value of each 
            // property of the entity.
            entityProperty.SetValue(entity, reader[entityProperty.Name], null);
        }

        list.Add(entity);
    }
    return list;
}

上述代码的缺点

  1. 使用反射给属性赋值是一种非常耗费性能的操作;
  2. 开发人在给属性赋值时必须确保类型安全。

以下代码将展示如何使用SqlDataReader读取数据并添加到实体列表,不必写事件的静态代码也不必重复地使用反射。

代码案例:遍历data reader以及实体列表中相关的值

// Method to be called for getting the list of entity class
// filled with data from the data reader. 
public List<T> ReadList<T>(SqlDataReader reader)
{
    var list = new List<T>();  
    Func<SqlDataReader, T> readRow = GetReader<T>(); 

    while (reader.Read())
    {
        list.Add(readRow(reader));
    }
    return list;
} 

代码案例:使用Microsoft .Net的表达式特性使用代码动态地创建一个方法,并保存到字典方便后续使用

// Method for creating the dynamic funtion for setting entity properties
public Func<SqlDataReader, T> GetReader<T>()
{
    Delegate resDelegate;
    if (!ExpressionCache.TryGetValue(typeof(T), out resDelegate))
    {
        // Get the indexer property of SqlDataReader 
        var indexerProperty = typeof(SqlDataReader).GetProperty("Item",
        new[] { typeof(string) });
        // List of statements in our dynamic method 
        var statements = new List<Expression>();
        // Instance type of target entity class 
        ParameterExpression instanceParam = Expression.Variable(typeof(T));
        // Parameter for the SqlDataReader object
        ParameterExpression readerParam =
            Expression.Parameter(typeof(SqlDataReader));

        // Create and assign new T to variable. Ex. var instance = new T(); 
        BinaryExpression createInstance = Expression.Assign(instanceParam,
            Expression.New(typeof(T)));
        statements.Add(createInstance);

        foreach (var property in typeof(T).GetProperties())
        {
            // instance.Property 
            MemberExpression getProperty =
            Expression.Property(instanceParam, property);
            // row[property] The assumption is, column names are the 
            // same as PropertyInfo names of T 
            IndexExpression readValue =
                Expression.MakeIndex(readerParam, indexerProperty,
                new[] { Expression.Constant(property.Name) });

            // instance.Property = row[property] 
            BinaryExpression assignProperty = Expression.Assign(getProperty,
                Expression.Convert(readValue, property.PropertyType));

            statements.Add(assignProperty);
        }
        var returnStatement = instanceParam;
        statements.Add(returnStatement);

        var body = Expression.Block(instanceParam.Type,
            new[] { instanceParam }, statements.ToArray());

        var lambda =
        Expression.Lambda<Func<SqlDataReader, T>>(body, readerParam);
        resDelegate = lambda.Compile();

        // Cache the dynamic method into ExpressionCache dictionary
        ExpressionCache[typeof(T)] = resDelegate;
    }
    return (Func<SqlDataReader, T>)resDelegate;
}  

上述代码中,我们使用Microsoft .Net表达式树动态地创建一个方法从传递给它的SqlDataReader对象中读取值只并赋值给实体对象。这方法被编译并缓存到字典中,所以它可以后续重复使用而不必重复创建。

这项技术的优点

  1. 开发人员无需为设置任何实体对象的值再写代码;
  2. 表达式树编写的方法可以被序列化并跨进程传输;
  3. 使用.Net API创建表达式树相比写IL Code(中间代码)更加简单。

总结

我们看了四个情景,反射技术被使用在不适当的地方导致性能变差,我们还研究了可以改善性能的其他技术来代替反射,以及通过使用缓存机制来避免重复使用反射。

本文中使用的缓存技术可以确保只使用一次反射检索信息,并使用多次。

使用轻量级的代码生成技术生成动态代码以及使用表达式树创建静态代理方法来实例化对象或赋值、读取属性值等,而不必使用反射。

原文链接:www.codeproject.com/Articles/503527/Reflection-optimization-techniques
本文仅供学习参考,版权归原作者所有,转载请标明文章出处

你可能感兴趣的:(经验)