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

时间:2016-10-27 19:18:11

标签: c# reflection types .net-assembly

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

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

  

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

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

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

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

<小时/>

代码:

以下部分包含重现问题的相关代码以及已设置项目的说明。

程序集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();
        }
    }
}
/// <summary>
/// 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.
/// </summary>
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, bool>((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;
        }

        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="assembly"></param>
        public List<IPlugin> 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架构:PluginConfiguration.xsd

    <?xml version="1.0" encoding="utf-8"?>
<xs:schema id="PluginConfiguration"
    targetNamespace="clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd"
    elementFormDefault="qualified"
    xmlns="clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd"
    xmlns:mstns="clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="PluginConfiguration">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Plugin" type="PluginType" minOccurs="1" maxOccurs="unbounded"/>
      </xs:sequence>
      <xs:attribute name="Author" type="xs:string"/>
      <xs:attribute name="Version" type="xs:string"/>
    </xs:complexType>
  </xs:element>

  <xs:complexType name="PluginType">
    <xs:attribute name="Name" type="xs:string" use="required"/>
    <xs:attribute name="MainClass" type="ClassType" use="required"/>
  </xs:complexType>

  <xs:simpleType name="ClassType">
    <xs:restriction base="xs:string">
      <xs:annotation>
        <xs:documentation>
          Matches class names that follow the format:  IDENTIFIER(.IDENTIFIER)*
          This pattern is necessary for validating fully qualified class paths.
        </xs:documentation>
      </xs:annotation>
      <xs:pattern value="^([_a-zA-Z]+([_a-zA-Z0-9])*)(\.?[_a-zA-Z]+([_a-zA-Z0-9])*)*$"/>
    </xs:restriction>
  </xs:simpleType>
</xs:schema>

PluginConfiguration.cs

namespace PluginAPI
{

    /// <remarks/>
    [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;

        /// <remarks/>
        [XmlElement("Plugin", typeof(Plugin))]
        public Plugin[] Plugins
        {
            get { return this.pluginField; }
            set { this.pluginField = value; }
        }

        /// <remarks/>
        [XmlAttribute()]
        public string Author
        {
            get { return this.authorField; }
            set { this.authorField = value; }
        }

        /// <remarks/>
        [XmlAttribute()]
        public string Version
        {
            get { return this.versionField; }
            set { this.versionField = value; }
        }
    }

    /// <remarks/>
    [XmlType(AnonymousType = true, Namespace = "clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd")]
    public partial class Plugin
    {
        private string mainClassField;
        private string nameField;

        /// <remarks/>
        [XmlAttribute()]
        public string MainClass
        {
            get { return this.mainClassField; }
            set { this.mainClassField = value; }
        }

        /// <remarks/>
        [XmlAttribute()]
        public string Name
        {
            get { return this.nameField; }
            set { this.nameField = value; }
        }
    }
}

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

<?xml version="1.0" encoding="utf-8" ?>
<PluginConfiguration xmlns="clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd"
                     Author="Nicholas Miller"
                     Version="1.0.0">
  <Plugin MainClass="Plugin1.MyPlugin" Name="MyPlugin"/>
</PluginConfiguration>

0 个答案:

没有答案