为什么使用反射后在 .NET 7 中出现强制转换错误?

Why am I getting a casting error in .NET 7 after using reflection?

提问人:Christopher Painter 提问时间:11/24/2022 最后编辑:Christopher Painter 更新时间:11/26/2022 访问量:282

问:

.NET 7 和 .NET Framework 中的反射和强制转换是否不同?我正在移植一个项目,并在移动代码后出现此强制转换错误。奇怪的是,这个类实现了该接口。此代码适用于 .NET 4.x。

    foreach (Assembly pluginAssembly in pluginAssemblies)
    {
        try
        {
            // Look for class(s) with our interface and construct them
            foreach (var type in pluginAssembly.GetTypes())
            {
                Type iDesigner = type.GetInterface(typeof(IFireworksDesigner).FullName);
                if (iDesigner != null)
                {
                    object instance = Activator.CreateInstance(type); // creates an object 
                    IFireworksDesigner designer  = (IFireworksDesigner)instance; // throws an exception
                    // do stuff
                }
            }
        }
        catch(Exception ex)
        {
            //Something really bad must have happened. 
            MessageBox.Show("Fatal error reflecting plugins in assembly '" + pluginAssembly.FullName + "'.\r\n" +
                "The error message is:\r\n\r\n" + ex.Message);
        }
    }

更新:我在 https://github.com/chrpai/reflection 上制作了一个示例存储库

使用调用 .NET Standard 2.0 的 FW48 EXE 即可正常工作。 使用 Core7 EXE 调用 .NET Standard 2.0 或 .NET 7 DLL 时,它将失败。

C# 反射 转换 NET-7.0

评论

0赞 Dai 11/24/2022
pluginAssembly.GetTypes()将返回不可构造的类型,例如类型和类型,以及没有无参数构造函数的类型。您需要先检查是否可以构造。interfaceabstract classclassvar type
0赞 Christopher Painter 11/24/2022
如果我理解正确的话,它确实构建了。Activator.CreateInstance(type) 返回一个对象,我可以在调试器中看到它的预期属性。我是不是误会了什么?
0赞 Dai 11/24/2022
抛出的确切异常是什么?它的类型、消息和调用堆栈是什么?
1赞 ProgrammingLlama 11/24/2022
有趣的是,使用代替或似乎有效。🤔 修改后的小提琴.Name.AssemblyQualifiedName.FullName
1赞 Dai 11/24/2022
我从来不喜欢这种方法:“字符串类型”的 API 并不好玩。(在我的不良 API 列表中的下一个:WinForms/WPF 中的所有属性......Type.GetInterface(String)Object DataSource

答:

3赞 shingo 11/26/2022 #1

我明白了原因,结论是类实现的接口和你试图转换的接口存在于不同的 s 中,所以它失败了。AssemblyLoadContext


LoadFile 的文档说:

LoadFile 不会将文件加载到加载自上下文中

这意味着将程序集加载到新的上下文中,源代码也会验证这一点。LoadFile

AssemblyLoadContext alc = new IndividualAssemblyLoadContext($"Assembly.LoadFile({normalizedPath})");
result = alc.LoadFromAssemblyPath(normalizedPath);

加载 LibCore.dll 后,程序中存在 2 个,它们都有一个同名和相同程序集名称的接口。因此,即使使用代码也无法区分不同的上下文。LoadFileAssemblyLoadContextIPlugintype.GetInterface(typeof(IPlugin).FullName)AssemblyQualifiedName

使用以下代码进行验证。

// IndividualAssemblyLoadContext #2
Console.WriteLine(AssemblyLoadContext.GetLoadContext(iDesigner.Assembly));
// DefaultAssemblyLoadContext #0
Console.WriteLine(AssemblyLoadContext.GetLoadContext(typeof(IPlugin).Assembly));

解决方案 1

使用代替 . 将程序集加载到加载自上下文中,以便可以共享接口。LoadFromLoadFileLoadFrom

pluginAssemblies.Add(Assembly.LoadFrom(file));

解决方案 2

使用独立核心库,例如,您可以有 3 个项目。

ConsoleCoreCore.exe

LibCore.dll
    public interface IPlugin
    public class Server

Lib1.dll
    public class LibStandardPlugin : IPlugin

Lib1.dll 和 ConsoleCoreCore.exe 都引用 LibCore.dll

ConsoleCoreCore.exe -> LibCore.dll <- Lib1.dll

现在,如果您从 LibCore.dll 加载 Lib1.dll ,Lib1.dll 将被加载到独立上下文中,但它的接口仍然是来自 LibCore.dll 的接口。 LoadFileIPlugin

// IndividualAssemblyLoadContext #1
Console.WriteLine(AssemblyLoadContext.GetLoadContext(type.Assembly));
// DefaultAssemblyLoadContext #0
Console.WriteLine(AssemblyLoadContext.GetLoadContext(iDesigner.Assembly));
// DefaultAssemblyLoadContext #0
Console.WriteLine(AssemblyLoadContext.GetLoadContext(typeof(IPlugin).Assembly));