为什么相同的类型不相等?

我正在开发一个小插件框架,并注意到我无法成功使用Type.IsAssignableFrom()来检查插件类是否实现了类型IPlugin 。 我检查了其他SO问题(比如这个) ,看看为什么函数返回false,但令我惊讶的是,没有一个建议有效。

我在插件程序PluginAPI.IPlugin有一个类,它在引用的程序PluginAPI.IPlugin实现PluginAPI.IPlugin类。 检查我的PluginAPI.IPlugin类型的AssemblyQualifiedName以及插件类的接口列表的类型时,我发现没有任何区别。 两者都超出了价值:

PluginAPI.IPlugin,PluginAPI,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null

经过进一步调查,我检查了Type.cs源代码 ,发现函数IsAssignableFrom()在内部调用中失败,它检查类型是否相等(via == )。

我发现修改我的插件框架以在典型的执行上下文( Assembly.LoadFrom )中加载插件程序集而不是仅reflection上下文( Assembly.ReflectionOnlyLoadFrom )允许类型相等性检查评估为true ,这是我所期望的一直。

为什么仅reflection上下文导致类型不再相等?


码:

以下部分包含用于复制问题的相关代码以及已设置的项目说明。

程序集1:PluginAPI

该程序集仅包含IPlugin.cs:

 namespace PluginAPI { public interface IPlugin { } } 

程序集2:Plugin1

该程序集包含几个插件(实现PluginAPI.IPlugin类)。

 namespace Plugin1 { public class MyPlugin : IPlugin { public MyPlugin() { } } } 

程序集3:AppDomainTest

此程序集包含入口点并测试将插件程序集加载到单独的AppDomain中,以便可以卸载它们。

 namespace AppDomainTest { public class AppDomainTest { public static void Main(String[] args) { AppDomain pluginInspectionDomain = AppDomain.CreateDomain("PluginInspectionDomain"); PluginInspector inspector = new PluginInspector(); pluginInspectionDomain.DoCallBack(inspector.Callback); AppDomain.Unload(pluginInspectionDomain); Console.ReadKey(); } } } 
 ///  /// If using this class. You can comment/uncomment at the lines provided /// where Assembly.Load* vs. Assembly.ReflectionOnlyLoad* are used. /// You should notice Assembly.Load* succeeds in determining type equality; /// however, Assembly.ReflectionOnlyLoad* fails. ///  namespace AppDomainTest { [Serializable] public class PluginInspector { public void Callback() { AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_ReflectionOnlyAssemblyResolve; //TODO: Change this to the output directory of the Plugin1.dll. string PluginDirectory = @"H:\Projects\SOTest\Plugin1\bin\Debug"; DirectoryInfo dir = new DirectoryInfo(PluginDirectory); if (dir.Exists) { FileInfo[] dlls = dir.GetFiles("*.dll"); //Check if the dll has a "Plugin.config" and if it has any plugins. foreach (FileInfo dll in dlls) { LoadAssembly(dll.FullName); } } AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= CurrentDomain_ReflectionOnlyAssemblyResolve; } private void LoadAssembly(string path) { //From within the PluginInspectionDomain, load the assembly in a reflection only context. Assembly[] loadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies(); //TODO (toggle comment): Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); AssemblyName assemblyName = AssemblyName.GetAssemblyName(path); bool assemblyAlreadyLoaded = loadedAssemblies.Any(new Func((Assembly a) => { //If the assembly full names match, then they are identical. return (assemblyName.FullName.Equals(a.FullName)); })); if (assemblyAlreadyLoaded) { //Assembly already loaded. No need to search twice for plugins. return; } //Assembly not already loaded, check to see if it has any plugins. Assembly assembly = Assembly.ReflectionOnlyLoadFrom(path); //TODO (toggle comment): Assembly assembly = Assembly.LoadFrom(path); GetPlugins(assembly); } private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args) { //This callback is called each time the current AppDomain attempts to resolve an assembly. //Make sure we check for any plugins in the referenced assembly. Assembly assembly = Assembly.ReflectionOnlyLoad(args.Name); //TODO (toggle comment): Assembly assembly = Assembly.Load(args.Name); if (assembly == null) { throw new TypeLoadException("Could not load assembly: " + args.Name); } GetPlugins(assembly); return assembly; } ///  /// This function takes an assembly and extracts the Plugin.config file and parses it /// to determine which plugins are included in the assembly and to check if they point to a valid main class. ///  ///  public List GetPlugins(Assembly assembly) { using (Stream resource = assembly.GetManifestResourceStream(assembly.GetName().Name + ".Plugin.config")) { if (resource != null) { //Parse the Plugin.config file. XmlSerializer serializer = new XmlSerializer(typeof(PluginConfiguration)); PluginConfiguration configuration = (PluginConfiguration)serializer.Deserialize(resource); if (configuration == null) { Console.WriteLine("Configuration is null."); } if (configuration.Plugins == null) { Console.WriteLine("Configuration contains no plugins."); } foreach (Plugin pluginDescriptor in configuration.Plugins) { bool containsType = false; foreach (Type type in assembly.GetExportedTypes()) { if (type.FullName.Equals(pluginDescriptor.MainClass)) { containsType = true; if (typeof(IPlugin).IsAssignableFrom(type)) { Console.WriteLine("MainClass \'{0}\' implements PluginAPI.IPlugin", pluginDescriptor.MainClass); } Console.WriteLine("Checking for {0}", typeof(IPlugin).AssemblyQualifiedName); Console.WriteLine("Interfaces:"); foreach (Type interfaceType in type.GetInterfaces()) { Console.WriteLine("> {0}", interfaceType.AssemblyQualifiedName); if (interfaceType == typeof(IPlugin)) { //This is NOT executed if in reflection-only context. Console.WriteLine("interface is equal to IPlugin"); } } } } Console.WriteLine((containsType ? "Found \'" + pluginDescriptor.MainClass + "\' inside assembly. Plugin is available." : "The MainClass type could not be resolved. Plugin unavailable.")); } } } return null; } } } 

如果对我如何使用XML指定插件配置感到好奇,请查看这些文件。 请注意,重新解决问题不需要XML解析。 例如,你可以硬编码一些类型的字符串来搜索和比较,但这就是我选择这样做的方式。 这也为那些有兴趣复制我的插件加载器的人提供了参考。

XML Schema :PluginConfiguration.xsd

                     Matches class names that follow the format: IDENTIFIER(.IDENTIFIER)* This pattern is necessary for validating fully qualified class paths.       

PluginConfiguration.cs

 namespace PluginAPI { ///  [XmlType(AnonymousType = true, Namespace = "clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd")] [XmlRoot(ElementName = "PluginConfiguration", Namespace = "clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd", IsNullable = false)] public partial class PluginConfiguration { private Plugin[] pluginField; private string authorField; private string versionField; ///  [XmlElement("Plugin", typeof(Plugin))] public Plugin[] Plugins { get { return this.pluginField; } set { this.pluginField = value; } } ///  [XmlAttribute()] public string Author { get { return this.authorField; } set { this.authorField = value; } } ///  [XmlAttribute()] public string Version { get { return this.versionField; } set { this.versionField = value; } } } ///  [XmlType(AnonymousType = true, Namespace = "clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd")] public partial class Plugin { private string mainClassField; private string nameField; ///  [XmlAttribute()] public string MainClass { get { return this.mainClassField; } set { this.mainClassField = value; } } ///  [XmlAttribute()] public string Name { get { return this.nameField; } set { this.nameField = value; } } } } 

Plugin.config (插件程序集中的嵌入式资源)。