【问题标题】:Type.IsAssignableFrom() bug [duplicate]Type.IsAssignableFrom() 错误 [重复]
【发布时间】:2011-02-05 01:45:56
【问题描述】:

我想在运行时加载一些插件(C# 程序集)并使用一些在它们内部实现特定接口的类。 所以我写了一个基础程序集(Interfaces.dll),我在其中声明了基础接口:

namespace Interfaces
{
    public interface IPlugin01
    {
        string Name { get; }
        string Description { get; }
        void Calc1();
    }

    public interface IPlugin02
    {
        void Calc2();
    }
}

然后,在两个不同的程序集(Plugin01.dll 和 Plugin02.dll)中,我使用类实现了这些接口:

namespace Plugin01
{
    public class Class1 : Interfaces.IPlugin01,Interfaces.IPlugin02
    {
        public string Name { get { return "Plugin01.Class1"; } }
        public string Description { get { return "Plugin01.Class1 description"; } }
        public void Calc1() { Console.WriteLine("sono Plugin01.Class1 Calc1()"); }
        public void Calc2() { Console.WriteLine("sono Plugin01.Class1 Calc2()"); }
    }

    public class Class2 : Interfaces.IPlugin01
    {
        public string Name { get { return "Plugin01.Class2"; } }
        public string Description { get { return "Plugin01.Class2 description"; } }
        public void Calc1() { Console.WriteLine("sono Plugin01.Class2 Calc1()"); }
    }
}

namespace Plugin02
{
    public class Class1 : Interfaces.IPlugin01
    {
        public string Name { get { return "Plugin02.Class1"; } }
        public string Description { get { return "Plugin02.Class1 description"; } }
        public void Calc1() { Console.WriteLine("sono Plugin02.Class1 Calc1()"); }
    }
    public class Class2 : Interfaces.IPlugin02
    {
        public void Calc2() { Console.WriteLine("sono Plugin02.Class2 Calc2()"); }
    }
}

最后是测试控制台应用:

//#define LIST1
#define LIST2

using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using System.Diagnostics;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
#if LIST1
            List<Interfaces.IPlugin01> list1 = GetFilePlugins<Interfaces.IPlugin01>(@".\Plugins\Plugin01.dll");
#else
            List<Interfaces.IPlugin01> list1 = new List<Interfaces.IPlugin01>();
#endif

#if LIST2
            List<Interfaces.IPlugin01> list2 = GetFilePlugins<Interfaces.IPlugin01>(@".\Plugins\Plugin02.dll");
#else
            List<Interfaces.IPlugin01> list2 = new List<Interfaces.IPlugin01>();
#endif
        /// ------------------------------------------------------------------------------
        /// If I don't load one of the assembly using LIST1 or LIST2
        /// GetDirectoryPlugins returns an empty list.
        /// The bug (is a bug or my mistake?) is inside GetFilePlugins:
        /// typeT.IsAssignableFrom(type) returns FALSE even if interface is implemented !!!
        /// Using LIST1 or LIST2 before using GetDirectoryPlugins makes
        /// typeT.IsAssignableFrom(type) return TRUE as expected !!!
        /// I'm going crazy over this....
        /// ------------------------------------------------------------------------------            
        List<Interfaces.IPlugin01> listtot = GetDirectoryPlugins<Interfaces.IPlugin01>(@".\Plugins\");


#if LIST1
            Console.WriteLine("--- 001 ---");
            foreach(Interfaces.IPlugin01 plugin in list1)
                plugin.Calc1();
#endif
#if LIST2
            Console.WriteLine("--- 002 ---");
            foreach (Interfaces.IPlugin01 plugin in list2)
                plugin.Calc1();
#endif
            Console.WriteLine("--- TOT ---");
            foreach (Interfaces.IPlugin01 plugin in listtot)
                plugin.Calc1();
            Console.ReadLine();
        }

        public static List<T> GetFilePlugins<T>(string filename)
        {
            List<T> ret = new List<T>();
            if (File.Exists(filename))
            {
                Type typeT = typeof(T);
                Assembly ass = Assembly.LoadFrom(filename);
                foreach (Type type in ass.GetTypes())
                {
                    if (!type.IsClass || type.IsNotPublic) continue;
                    Debug.Print("{0} <- {1}", typeT.IsAssignableFrom(type), type);
                    if (typeT.IsAssignableFrom(type))
                    {
                        T plugin = (T)Activator.CreateInstance(type);
                        ret.Add(plugin);
                    }
                }
            }
            return ret;
        }
        public static List<T> GetDirectoryPlugins<T>(string dirname)
        {
            List<T> ret = new List<T>();
            string[] dlls = Directory.GetFiles(dirname, "*.dll");
            foreach (string dll in dlls)
            {
                List<T> dll_plugins = GetFilePlugins<T>(Path.GetFullPath(dll));
                ret.AddRange(dll_plugins);
            }
            return ret;
        }
    }
}

正如评论中所写,如果我将两行 #define LIST1#define LIST2 都留下评论,即使我的类实现了所需的接口,IsAssignableFrom() 也会返回 false。为什么?

【问题讨论】:

  • 你为什么要重新发明轮子?看看 MEF - 所有这些都已经涵盖了。
  • 查看mef.codeplex.com 了解概览 - 这是内置于 .NET 4.0 中的

标签: c# plugins reflection


【解决方案1】:

已经有相同问题的答案。
打印完全限定名称,如 suggested here
您也可以尝试拨打GetInterface并检查null

【讨论】:

    【解决方案2】:

    关于直观代码和可读性的旁注,我认为这与这里的混淆有关:

    当应用于测试继承或检测接口实现时,Type.IsAssignableFrom 方法的名称含糊不清。用于这些目的的以下包装器会更有意义:

        public static bool CanBeTreatedAsType(this Type CurrentType, Type TypeToCompareWith)
        {
            // Always return false if either Type is null
            if (CurrentType == null || TypeToCompareWith == null)
                return false;
    
            // Return the result of the assignability test
            return TypeToCompareWith.IsAssignableFrom(CurrentType);
        }
    

    然后,可以使用更易于理解的客户端语法,例如:

        bool CanBeTreatedAs = typeof(SimpleChildClass).CanBeTreatedAsType(typeof(SimpleClass));
        CanBeTreatedAs = typeof(SimpleClass).CanBeTreatedAsType(typeof(IDisposable));
    

    这种方法代替 'is' 关键字的优点是它可以在运行时用于测试未知的任意类型,而 'is' 关键字(和通用类型参数)需要编译时知识特定类型。

    【讨论】:

      猜你喜欢
      • 2011-06-14
      • 2011-08-06
      • 2011-07-28
      • 2019-11-29
      • 1970-01-01
      • 2011-08-30
      • 2012-09-13
      • 2017-03-19
      • 2019-03-27
      相关资源
      最近更新 更多