net core开发技巧之使用反射 (一)

787 阅读3分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

使用反射减少代码

📢 1.1 反射的弊端

  • 反射可以在运行时动态获取程序信息。但也正因为需要运行时解析Class。导致了它的性能比普通调用的慢
  • 增加了程序复杂性
  • 打破了一些程序约定(私有字段与私有构造函数)

📢 1.2 使用Expression优化反射

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。 你可以对表达式树中的代码进行编辑和运算。 这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。 有关 LINQ 中表达式树的详细信息,请参阅如何使用表达式树生成动态查询 (C#)

请看例子:

  public class HashEntryMapper<TModel> where TModel : class
  {
        public static Func<HashEntry[], TModel> BuildResolver(HashEntry[] entries)
        {
            var source = typeof(HashEntry[]);
            var target = typeof(TModel);
            var p = Expression.Parameter(source, "p");
            List<MemberAssignment> bindsList = new List<MemberAssignment>(entries.Length);

            var props = target.GetProperties();
            PropertyInfo property;
            foreach (var entry in entries)
            {
                property = props.FirstOrDefault(x => x.Name.Equals(entry.Name));
                bindsList.Add(Expression.Bind(property, Expression.Constant(Convert.ChangeType(entry.Value, property.PropertyType))));
            }
            var targetNew = Expression.New(target);
            var lambda = Expression.MemberInit(targetNew, bindsList.ToArray());
            return Expression.Lambda<Func<HashEntry[], TModel>>(lambda, p).Compile();
        }
 }

StackExchange.Redis中,从我们实际的实体与StackExchange.Redis经常需要转换,这时我们可以把TModel 也就是实体类型传入,然后依据类型的信息实例化Model。如果将编译后的委托缓存起来。那么调用这个方法的效率会和原生相差无几。

📢 1.3 优化Option模式

在.net Framework年代,我们通常是通过 [ConfigurationManager]来获取配置项,ASP.NET Core 中新增了一个选项模式。

选项模式使用类来提供对相关设置组的强类型访问。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:

在Startup中绑定配置:

public void ConfigureServices(IServiceCollection services) 
{
    //Configuration.GetSection这个节点名称是自定义的
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
}

在Controller中的使用,更多用法请参考这里 微软官方文档

public class HomeController:ControllerBase
{
    private readonly IOption<MyOptions> _myOptions;
    
    public HomeController(IOption<MyOptions> options)
    {
       _myOptions = options;
    }
}

当我们的项目逐渐庞大之后,很容易忘记配置 services.Configure 这行代码,为了避免重复工作。可以来使用一下反射一键配置一下:

/// <summary>
/// 标识该Class是否是Option
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ConfigOptionAttribute : Attribute
{
}

public static class ConfigurationExtensions
{
    /// <summary>
    /// 添加程序集的配置
    /// </summary>
    /// <param name="services"></param>
    /// <param name="configuration"></param>
    /// <param name="assemblies"></param>
    public static void AddConfigurationAssemblies(this IServiceCollection services, IConfiguration configuration, params Assembly[] assemblies)
    {
        var attibuteType = typeof(ConfigOptionAttribute);

        var methodInfo = typeof(OptionsConfigurationServiceCollectionExtensions)
            .GetMethod("Configure", new Type[] { typeof(IServiceCollection), typeof(IConfiguration) });


        if (assemblies.IsNilSet())
        {
            assemblies = new[] { Assembly.GetCallingAssembly() };
        }

        if (methodInfo != null)
        {
            var optionNames = configuration.GetChildren().Select(x => x.Key).ToArray();
            foreach (var assembly in assemblies)
            {
                var types = assembly.ExportedTypes
                    .Where(x => x.IsDefined(attibuteType, false) && optionNames.Contains(x.Name)).ToArray();

                foreach (var type in types)
                {
                    var method = methodInfo.MakeGenericMethod(type);
                    method.Invoke(null, new object[] { services, configuration.GetSection(type.Name) });
                }
            }
        }
    }
}

上述代码,添加了一个ConfigOptionAttribute,在class上定义该特性可以让AddConfigurationAssemblies方法识别该类为Option配置类,再将services.Configure... 那行代码使用反射调用。下次就不会忘记写配置拉。。。

写在最后

本文描述了反射的一些使用场景,以及性能优化。在文章代码的最后留白了一部分,你是否可以将AddConfigurationAssemblies使用Expression 或 Emit 提高其调用效率?