C
C#•3d ago
the1mason

typeof(T).IsAssignableFrom does not work as I expected

There is a method in a generic class PluginLoader<TPlugin> : IPluginLoader<TPlugin>:
private static TPlugin? CreatePlugin(Assembly assembly)
{
var types = assembly.GetTypes().Where(t => typeof(TPlugin).IsAssignableFrom(t)).ToArray();
return types.Length switch
{
> 1 => throw new ApplicationException(
$"Found more than 1 type, implementing IPlugin in assembly {assembly.FullName}"),
< 1 => throw new ApplicationException(
$"Found 0 types, implementing IPlugin in assembly {assembly.FullName}"),
_ => (TPlugin?)Activator.CreateInstance(types.First()) ?? default
};
}
private static TPlugin? CreatePlugin(Assembly assembly)
{
var types = assembly.GetTypes().Where(t => typeof(TPlugin).IsAssignableFrom(t)).ToArray();
return types.Length switch
{
> 1 => throw new ApplicationException(
$"Found more than 1 type, implementing IPlugin in assembly {assembly.FullName}"),
< 1 => throw new ApplicationException(
$"Found 0 types, implementing IPlugin in assembly {assembly.FullName}"),
_ => (TPlugin?)Activator.CreateInstance(types.First()) ?? default
};
}
There is also an interface IWebPlugin with implementation Plugin1 This method is called with an assembly, containing Plugin1 and tries to create an instance of it. It worked before I made PluginLoader generic, but now typeof(TPlugin).IsAssignableFrom(t) returns false. Plugin1.GetInterface("IWebPlugin") returnsIWebPlugin, typeof(TPlugin gets IWebPlugin too, but the original condition fails nevertheless. Here's my result of playing with the debugger
var debugTypes = assembly.GetTypes()
{System.RuntimeType[1]}
[0]: {Plugin1.Plugin1}

var iWebPlugin = debugTypes[0].GetInterface("IWebPlugin")
{RainCrab.Plugins.AspNet.IWebPlugin}


var iaf = iWebPlugin.IsAssignableFrom(debugTypes[0])
true

var iafGeneric = typeof(TPlugin).IsAssignableFrom(debugTypes[0])
false

typeof(TPlugin)
{RainCrab.Plugins.AspNet.IWebPlugin}
var debugTypes = assembly.GetTypes()
{System.RuntimeType[1]}
[0]: {Plugin1.Plugin1}

var iWebPlugin = debugTypes[0].GetInterface("IWebPlugin")
{RainCrab.Plugins.AspNet.IWebPlugin}


var iaf = iWebPlugin.IsAssignableFrom(debugTypes[0])
true

var iafGeneric = typeof(TPlugin).IsAssignableFrom(debugTypes[0])
false

typeof(TPlugin)
{RainCrab.Plugins.AspNet.IWebPlugin}
Why if Plugin1 implements IWebPlugin and typeof(TPlugin) is IWebPlugin, Plugin1 is not assignable from IWebPlugin?
6 Replies
reflectronic
reflectronic•3d ago
istypeof(TPlugin) == debugTypes[0].GetInterface("IWebPlugin") true? is typeof(TPlugin) == typeof(IWebPlugin) true?
the1mason
the1masonOP•3d ago
I have tried to debug it for a while and as far as I can tell, the problem is not really in this method. I have a prototype project that uses the same mechanism to resolve assemblies and create instances of IWebPlugin as in this case. It seems to me that I have messed up elsewhere but now it's too late and the debugger went nuts, so I'll try to solve it tomorrow and post an answer for history.
reflectronic
reflectronic•3d ago
yes, my suspicion is that you have loaded these plugins incorrectly in such a way that there are two different IWebPlugin interfaces the things i suggested would have confirmed that if it was the case
SleepWellPupper
SleepWellPupper•2d ago
I know this unrelated to your question, but important aspect to keep in mind when writing plugin systems via assembly loading is the potential memory leak when not unloading the assembly properly after the plugin is not needed anymore. Also, sandboxing. But that's another issue.
the1mason
the1masonOP•15h ago
I have an implementation that unloads assemblies but it does not prevent all memory leaks 'cause it's up to plugin's dev to ensure that when needed everything shuts down properly and there are no references to anything in the loaded context, so it can be collected by the GC when allowed Also I have the solution to my problem 😎 The structure of my projects is the following: - PluginBase - NuGet package w/ the loader and IWebPlugin interface - Plugin1 - A test plugin - MVC - Web project MVC references PluginBase Plugin1 references PluginBase but even though they reference the same package with the same version, they reference their respective local copies, created on build action. It resulted in 2 absolutely identical IWebPlugin types loaded with the only difference being Module properties linking to different sources. The solution I have found after some thinking with @Left2Dotnet First, I have to exclude PluginBase from the build outptut, adding to the <PackageReference...>
<ExcludeAssets> runtime </ExcludeAssets>
<ExcludeAssets> runtime </ExcludeAssets>
Then I want to allow my Plugin1 to get and load the .dll of the PluginBase from MVC's dll folder. I did this by using .runtimeconfig.json I have created Plugin1.runtimeconfig.template.json in the Plugin1's project root and added the following:
{
"runtimeOptions": {
"additionalProbingPaths": [
"../."
]
}
}
{
"runtimeOptions": {
"additionalProbingPaths": [
"../."
]
}
}
It will try to look for .dll dependencies in the parent folder too (because plugins are located in /Plugins folder) And the problem is officially solved The original prototype worked because I used a direct ProjectReference in both MVC and Plugin projects thus the .dll was the same and types did match fully
the1mason
the1masonOP•15h ago
Deep-dive into .NET Core primitives: deps.json, runtimeconfig.json,...
Blog posts about software development, plus some other stuff.

Did you find this page helpful?