Type.IsAssignableFrom() bug [duplicate]
I'd like to load some plugin (C# assembly) at runtime and use some of the classes implementing a certain interface inside each of them. So I wrote a base assemby (Interfaces.dll) in which I declare base interfaces:
namespace Interfaces
{
public interface IPlugin01
{
string Name { get; }
string Description { get; }
void Calc1();
}
public interface IPlugin02
{
void Calc2();
}
}
Then, in two different assemblies (Plugin01.dll and Plugin02.dll) I've implemented those interfaces using classes:
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()"); }
}
}
and
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()"); }
}
}
Finally the test console app:
//#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;
}
}
}
As written in comment, if I leave both rows #define LIST1 and #define LIST2 commented, IsAssignableFrom() returns fals开发者_如何转开发e even if my class implements desired interface. Why?
There is already answer to the same question.
Print the fully qualified names, as suggested here.
You can also try calling GetInterface
and checking for null
.
On a side-note regarding intuitive code and readability, which I think is related to the confusion here:
The name of the Type.IsAssignableFrom method is vague and confusing when applied to testing inheritance or detecting interface implementations. The following wrapper for these purposes would make a lot more sense:
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);
}
Then, one can have more understandable client syntax such as:
bool CanBeTreatedAs = typeof(SimpleChildClass).CanBeTreatedAsType(typeof(SimpleClass));
CanBeTreatedAs = typeof(SimpleClass).CanBeTreatedAsType(typeof(IDisposable));
The advantage of this method instead of the 'is' keyword is that it can be used at run-time to test unknown, arbitrary Types, whereas the 'is' keyword (and a generic Type parameter) requires compile-time knowledge of specific Types.
精彩评论