65.9K
CodeProject 正在变化。 阅读更多。
Home

反射优化技术

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (61投票s)

2012年12月4日

CPOL

9分钟阅读

viewsIcon

151190

本文介绍了一些 .NET 技术,用于最优且高效地使用反射。

引言

反射是 Microsoft .NET 框架中一项非常强大的功能。它在 System.Reflection 命名空间下提供了一套丰富的 API,用于在运行时动态加载和处理程序集和对象,以及检索元数据信息,例如方法信息、属性信息和特性(注解)。

使用反射确实能让开发者的生活变得轻松,但是它应该被最小化使用,换句话说,只在需要时使用;因为它在很大程度上会影响性能。有一些不同的技术可以帮助高效地使用反射。

很多时候,我们在不了解反射带来的开销的情况下使用它。在本文中,我们将探讨一些有助于优化反射使用的技术。

解决方案

在本文中,我们将讨论一些可以用来完全避免反射或至少避免重复使用反射的技术。在文章的其余部分,我们将探讨一些可以有效使用反射的场景。

场景 1 – 动态调用方法

如果在开发时就知道要在运行时创建的对象上调用的方法/成员的名称,那么就应该使用接口并静态调用它们,而不是使用反射来动态调用它们。

完全动态调用的示例

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. 用于进行方法调用等的代码更短。

场景 2 – 读取类的自定义属性

自定义属性(注解)用于提供有关程序集、类或类成员的额外信息。因此,如果一个类带有每次创建该类实例时都需要用到的属性,那么建议将该值缓存在静态变量中,这样反射就只用于第一个实例,而可以避免用于后续实例。

以下是一个实体类的示例

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

        // use curTableInfo here 
    }
}

上面的示例代码演示了如何只从自定义属性中检索一次信息,并将其用于同一类的后续实例,而无需每次都运行反射代码。

使用此技术的好处是:

  1. 反射只在第一次应用,而信息的缓存副本用于所有后续时间。
  2. 通过使用缓存技术,反射的成本被最小化。

场景 3 – 依赖注入

很多时候,我们需要在某些代码块中注入依赖项,以便将特定功能的实现逻辑与其使用者逻辑解耦,从而允许不同的提供者在以后提供实现。为了处理这种情况,我们通常允许用户在配置文件中配置要注入的程序集和类。

在这种情况下,应用程序将需要加载程序集并使用反射动态创建依赖项类实例,每次需要时都会这样做;此操作在处理方面非常昂贵,并且在这种应用程序的这部分流量很大时会导致性能下降。

Microsoft .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;
} 

使用上述代码的缺点是: 

  1. 每次需要依赖项类的实例时,都会使用反射动态创建它。
  2. 使用反射动态创建对象是一项非常昂贵的操作。

以下代码演示了如何避免反射的成本: 

// Method that needs to create the instance of DefaultSecuirtyProvider
// class dynamically
private void CreateInstance()
{
    Type classType = 
   Type.GetType("DefaultSecurityProvider, AssemblyName");

    // Following function returns the delegate for instantiating the required
    // object statically
    CreateInstanceDelegate createInstance = ObjectInstantiater(classType);

    // Executing the delegate for creating an instance of the dependency class
    ISecurityProvider employeeInstance = createInstance() as ISecurityProvider;
}  

以下委托将用于映射使用 Microsoft .NET 轻量级代码生成技术动态创建的方法。 

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

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

// Dictionary for holding object instantiator delegates
private static Dictionary<Type, CreateInstanceDelegate> 
    _createInstanceDelegateList = new Dictionary<Type, 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 动态创建了一个代理方法,该方法静态地创建了依赖项类的实例。一旦创建了这个方法,我们就会将其存储在一个字典中,以依赖项类的类型作为键,以便以后可以从字典中提取并使用这个委托。

使用此技术的好处是:

  1. 依赖项类的对象是使用 Object 实例化方法静态创建的。
  2. 此技术有助于避免对象实例化的反射。

场景 4 – 动态设置 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>();  

以下代码从数据读取器填充员工列表

// 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 对象读取值并将其添加到实体列表中。

以下函数迭代数据读取器,并使用相关值填充实体列表。

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

以下字典保存了为将数据表值填充到实体列表而动态生成的方法的委托。 

// Cache store for memorizing the delegate for later use
ConcurrentDictionary<Type, Delegate> ExpressionCache = 
                       new ConcurrentDictionary<Type, Delegate>();

以下方法使用 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;
}   

在上面的代码中,我们使用 .NET 表达式树功能动态创建了一个函数,该函数在读取 SqlDataReader 对象并将其作为参数传递后,设置实体对象的值。该方法被编译并缓存在字典中,以便以后可以使用它,而无需重新创建它。

使用此技术的好处是:

  1. 开发人员无需编写设置任何实体值的代码。
  2. 为方法创建的表达式树可以被序列化并在进程边界之间传输。
  3. 使用 .NET API 创建表达式树比编写 IL 代码更容易。

结论

我们探讨了四种不恰当地使用反射技术导致性能不佳的场景,并且我们还探讨了可以通过使用反射的替代方案或使用缓存机制避免重复使用反射来提高性能的技术。

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

使用轻量级代码生成技术和表达式树进行的动态代码生成有助于创建静态代理方法,以避免在实例化对象和设置/获取属性值等场景中使用反射。

进一步阅读

© . All rights reserved.