面试题答案
一键面试设计思路
- 定义插件标记特性
- 创建一个自定义特性,例如
[PluginAttribute]
,用于标记插件类。这个特性可以添加一些基本信息,如插件名称、描述等。
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class PluginAttribute : Attribute { public string Name { get; set; } public string Description { get; set; } public PluginAttribute(string name, string description) { Name = name; Description = description; } }
- 创建一个自定义特性,例如
- 定义功能方法标记特性
- 对于插件中的功能方法,创建
[PluginFunctionAttribute]
特性,标记这些方法,可能还需要添加一些方法相关的元数据,如方法的功能类别等。
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class PluginFunctionAttribute : Attribute { public string FunctionCategory { get; set; } public PluginFunctionAttribute(string functionCategory) { FunctionCategory = functionCategory; } }
- 对于插件中的功能方法,创建
- 插件发现
- 使用反射在指定的程序集或目录中查找标记了
[PluginAttribute]
的类。可以缓存查找结果,避免每次都进行反射查找。例如,可以在应用启动时扫描一次插件目录,并将找到的插件类型缓存起来。
private static List<Type> DiscoverPlugins(string pluginDirectory) { var pluginTypes = new List<Type>(); var dlls = Directory.GetFiles(pluginDirectory, "*.dll"); foreach (var dll in dlls) { try { var assembly = Assembly.LoadFrom(dll); var types = assembly.GetTypes(); foreach (var type in types) { if (type.IsDefined(typeof(PluginAttribute), false)) { pluginTypes.Add(type); } } } catch (Exception ex) { // 处理加载程序集失败的异常 Console.WriteLine($"Error loading assembly {dll}: {ex.Message}"); } } return pluginTypes; }
- 使用反射在指定的程序集或目录中查找标记了
- 插件加载
- 一旦发现插件类型,使用
Activator.CreateInstance
来创建插件实例。同样可以考虑缓存实例,特别是对于单例模式的插件。
private static object LoadPlugin(Type pluginType) { return Activator.CreateInstance(pluginType); }
- 一旦发现插件类型,使用
- 功能方法调用
- 对于加载的插件实例,使用反射查找标记了
[PluginFunctionAttribute]
的方法,并可以根据方法的元数据(如FunctionCategory
)进行调用。这里也可以缓存方法信息,减少反射开销。
private static void InvokePluginFunction(object pluginInstance, string functionCategory) { var methods = pluginInstance.GetType().GetMethods() .Where(m => m.IsDefined(typeof(PluginFunctionAttribute), false) && ((PluginFunctionAttribute)m.GetCustomAttribute(typeof(PluginFunctionAttribute))).FunctionCategory == functionCategory); foreach (var method in methods) { method.Invoke(pluginInstance, null); } }
- 对于加载的插件实例,使用反射查找标记了
性能优化
- 缓存反射结果
- 如上述代码中提到的,缓存插件类型和方法信息。一旦扫描并发现插件和其功能方法,将这些信息存储在内存中,下次需要使用时直接从缓存中获取,避免重复的反射操作。
- 减少不必要的反射操作
- 在发现插件和调用功能方法时,尽量只进行必要的反射。例如,在发现插件时,只查找标记了特定特性的类型;在调用功能方法时,提前筛选出符合条件的方法,而不是每次都对所有方法进行反射操作。
- 使用动态编译
- 对于频繁调用的插件功能方法,可以考虑使用动态编译技术(如
System.Reflection.Emit
或Roslyn
)将反射操作转化为直接的方法调用,从而提高性能。不过这种方法实现较为复杂,需要权衡使用场景。
- 对于频繁调用的插件功能方法,可以考虑使用动态编译技术(如
- 延迟加载
- 对于插件的加载,可以采用延迟加载策略。即只有在真正需要使用某个插件时才进行加载,而不是在应用启动时全部加载,这样可以减少应用启动时的性能开销。