26

I am working on a plugin system that loads .dll's contained in a specified folder. I am then using reflection to load the assemblies, iterate through the types they contain and identify any that implement my IPlugin interface.

I am checking this with code similar to the following:

foreach(Type t in myTypes )
{
    if( typeof(IPlugin).IsAssignableFrom(t) )
    {
       ...
    }
}

For some reason IsAssignableFrom() keeps returning false when it should be returning true. I have tried replacing the t by explicitly giving it a type that should pass, and it works fine, but for some reason it isn't working with the types that are returned from the loaded assembly. To make things stranger, the code works fine on my co-worker's machine but not on mine.

Does anyone know of anything that might cause this sort of behavior?

Thanks

Sam Holder
  • 32,535
  • 13
  • 101
  • 181
bingles
  • 11,582
  • 10
  • 82
  • 93

9 Answers9

35

That typically happens when there's a mismatch between the assembly which contains the type IPlugin that the current assembly references, and the assembly which is referenced by the assembly containg the types you're iterating over.

I suggest you print:

typeof (IPlugin).Module.FullyQualifiedName

and

foreach (var type in t.GetInterfaces ()) 
{    
    Console.WriteLine (type.Module.FullyQualifiedName)
}

To see where the mismatch is.

Sam Holder
  • 32,535
  • 13
  • 101
  • 181
Jb Evain
  • 17,319
  • 2
  • 67
  • 67
  • In my case, type comparison fails only in case of one interface and it goes well for all other interfaces defined in the same assembly ! Why would that happen? The assembly containing interface is copied to the application's "bin\debug" folder using post-build event of the project. So I think that the assembly referenced in my start-up project (using direct project reference) is same as the one copied in the application's "bin\debug" folder. Is that not the case? – Learner Dec 20 '13 at 08:34
  • This was it for me. My module was refencing v2.0.0.0 of the assembly containing the type I was checking, whilst the type implementing that was implementing a type with the same name from v2.1.0.0 of that assembly. – James Wiseman Jul 27 '16 at 15:46
  • 6
    It appears to me that this fails if the assemblies are loaded into a reflection only context. Even though both types have the same *AssemblyQualifiedName*. – Nicholas Miller Oct 27 '16 at 18:19
13

I had same issue when interface was defined in a separate assembly to implementing type. Iterating and loading assemblies from root folder that contained dlls with classes AND dll with interface resulted in type mismatch as mentioned above.

One solution was to change LoadFrom() to LoadFile() The LoadFrom method has some disadvantages and that is one of them:

If an assembly with the same identity is already loaded, LoadFrom returns the loaded assembly even if a different path was specified.

Another way to overcome this is to place all dlls with types implementing interface into separate folder and not to copy referenced assembly (CopyLocal = False) so Assembly.LoadFrom will not load dll containing interface in memory.

Dovydas Šopa
  • 2,282
  • 8
  • 26
  • 34
elm
  • 339
  • 5
  • 12
11

Some other answers have mentioned the lack of clarity in the name of the IsAssignableFrom method. I agree, and as a result was using it in the wrong way.

Try a little experimenting with reversing the objects in your code and see if it works. For example:

Replace:

if (typeof(IPlugin).IsAssignableFrom(t))

with:

if (t.IsAssignableFrom(typeof(IPlugin)))

By doing this I not only got it to work, but began to understand what this method actually does.

kad81
  • 10,712
  • 3
  • 38
  • 44
  • 5
    Actually, this sounds incorrect. According to the docs (https://msdn.microsoft.com/pl-pl/library/system.type.isassignablefrom(v=vs.110).aspx) true is returned when *the current instance is an interface that c implements*. This makes `i.IsAssignableFrom(t)` valid in this scenario. – Wiktor Zychla Apr 25 '16 at 09:28
1

Sometimes it's an issue with the dynamic assembly referencing another assembly.

One simple thing to do it to disable local copy on the assembly (in visual studio right click the reference and set copy local to false). This should make it easier to nail down the directory where the assembly lives.

You can also implement an assembly resolver in-case .NET doesn't know how to initalize the type.

        AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler([Resolve Function]);
1

If you are using .net core and are following the tutorial on this link then please make sure you remember to add this to your plugin project

<ItemGroup>
    <ProjectReference Include="..\PluginBase\PluginBase.csproj">
        <Private>false</Private>
        <ExcludeAssets>runtime</ExcludeAssets>
    </ProjectReference>
</ItemGroup>

as described in the link. You get the above error if you don't. This also helped me

Bryida
  • 482
  • 6
  • 9
0

I work in Java which has the same API method and I just can't get my mind to grok it when reading the code (for some reason); therefore, I always read it in reverse as, in your case, "t is assignable to IPlugin). So if C# has "is" as Jonathon suggests, I would always use it - when reflecting in Java "instanceof" does not work for Class objects, only instances of the object.

Lawrence Dol
  • 63,018
  • 25
  • 139
  • 189
0

I had a similar issue which was caused due to dependency injection into plugins. Assume that assembly contains a definition of MyConcreteClass which implements ISomeInterface. MyConcreteClass constructor requires the dependencies I added to the serviceProvider. I am trying to instantiate the object using

 using Microsoft.Extensions.DependencyInjection;

 private IEnumerable<ISomeInterface> CreatePlugins(Assembly assembly)
 {
        ServiceCollection serviceCollestion = new ServiceCollection();
        serviceCollestion.AddSingleton(dependency1);
        serviceCollestion.AddSingleton(dependency2);
        serviceCollestion.AddSingleton(dependency3);
        var serviceProvider = serviceCollestion.BuildServiceProvider();

        foreach (Type type in assembly.GetTypes())
        {
            if (typeof(My.Namespace.ISomeInterface).IsAssignableFrom(type))
            {
                ISomeInterface result = ActivatorUtilities.CreateInstance(serviceProvider, type) as ISomeInterface;
            }
        }
 }

I got the exception:

Unable to resolve service for type 'My.Namespace.ISomeInterface' while attempting to activate 'Another.Namespace.MyConcreteClass'. at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider) at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)

I then found out that the process which called ActivatorUtilities.CreateInstance was already referencing a dependency of MyConcreteClass and that's why the instantiation failed. In order resolve this I followed the advice from the link below and cleared the folder of Another.Namespace from any assembly referenced by my process prior to loading it.

.NET: Unable to cast object to interface it implements

0

In my case was a dependency issue (even if the module names were identical). Check and add your level 1 dependencies for the plugin but they are not in the main app/shell (e.g. Oracle dll, some other nuget that is used only by the plugin). Once they are there (i.e. next to the plugin), it will return true.

Eusebiu Marcu
  • 320
  • 1
  • 10
-2

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 application code 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.

Mark Jones
  • 2,024
  • 1
  • 19
  • 12