这是我参与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 中新增了一个选项模式。
选项模式使用类来提供对相关设置组的强类型访问。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:
- 接口分隔原则 (ISP) 或封装:依赖于配置设置的方案(类)仅依赖于其使用的配置设置。
- 关注点分离:应用的不同部件的设置不彼此依赖或相互耦合。
在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 提高其调用效率?