操作系统:Ubuntu 24.04 Linux 框架:.NET 8.0.10 IDE:Rider(最新)
我正在从本机 Linux 项目创建自定义 .NET 主机来运行托管代码。
问题是我在大约 50% 的情况下比较类型时遇到逻辑矛盾。该问题 100% 可重现,并且仅发生在 Linux 上且使用自定义 .NET 主机。
它还会影响
GetCustomAttributes
方法的执行,在这种情况下找不到属性。调试使我看到了屏幕截图中可见的情况,其中类型不匹配,但当我在调试器中比较它们时(参见 watch 变量),它们确实匹配。
如果我直接将托管代码作为控制台可执行文件运行,而不需要自定义 .NET 主机,则完全相同的代码可以很好地工作(可以匹配所有类型)。
我按照 Microsoft 的说明编写了自定义 .NET 主机: https://learn.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting
除了上述问题之外,.NET Host 运行良好,可以加载所有程序集,并且所有其他代码似乎运行良好。
我在这里想念什么? .NET 主机配置错误?以某种方式加载 Typo 对象两次? CLR 或 JIT 错误?
更新:
我检查了两个类型对象上的装配位置,它们是完全相同的。
这是类型确实不同的提示:
type.GetHashCode() = {int} 18014707
stopAtType.GetHashCode() = {int} 59387592
Loaded Assembly '/home/viktor/se-dotnet-server-local/SpaceEngineersDedicated/bin/Debug/net8.0/Sandbox.Game.dll'
Loading module /home/viktor/se-dotnet-server-local/SpaceEngineersDedicated/bin/Debug/net8.0/Sandbox.Game.dll in application domain 1:clr_libhost
Symbols for module /home/viktor/se-dotnet-server-local/SpaceEngineersDedicated/bin/Debug/net8.0/Sandbox.Game.dll loaded
...
Loaded Assembly '/home/viktor/se-dotnet-server-local/SpaceEngineersDedicated/bin/Debug/net8.0/Sandbox.Game.dll'
Loading module /home/viktor/se-dotnet-server-local/SpaceEngineersDedicated/bin/Debug/net8.0/Sandbox.Game.dll in application domain 1:clr_libhost
Symbols for module /home/viktor/se-dotnet-server-local/SpaceEngineersDedicated/bin/Debug/net8.0/Sandbox.Game.dll loaded
在
AssemblyLoadContext
顶部打印 Main
实例:
foreach (var assemblyLoadContext in AssemblyLoadContext.All)
{
Console.WriteLine($"AssemblyLoadContext: {assemblyLoadContext.Name}");
}
Console.WriteLine($"AssemblyLoadContext.Default: {AssemblyLoadContext.Default.Name}");
直接运行(无需自定义.NET Host):
AssemblyLoadContext: Default
AssemblyLoadContext.Default: Default
通过自定义 .NET 主机运行:
AssemblyLoadContext: IsolatedComponentLoadContext(/home/viktor/se-dotnet-server-local/SpaceEngineersDedicated/bin/Debug/net8.0/SpaceEngineersDedicated.dll)
AssemblyLoadContext: Default
AssemblyLoadContext.Default: Default
它应该能解释问题。
代码
相关 .NET Host C++ 代码的要点:https://gist.github.com/viktor-ferenczi/feff098dfe27bba0f03929a89e2994a4
使用的运行时配置的要点:https://gist.github.com/viktor-ferenczi/9ba57aa40510ccd928e80570ddd54983
使用适当的值调用
RunManagedCode
函数。托管代码启动,因此传递的字符串是好的。
(该项目的其余部分现在一团糟,编译起来非常复杂,所以它不是公开的,但如果我成功的话,最终会公开的。)
您几乎发现了发生了什么(双加载程序集),但我可以从概念上解释它。
看我的评论:
System.Type.FullName
不能是类型的身份;它从来不是为此目的而设计的。它只不过是一个完全限定的名称。但是,不同的类型可以具有相同的命名空间和相同的名称。要查看它,请考虑以下示例:
组装1:
namespace NameMatch {
public class Demo { }
public class AssemblyIndicator1 { }
}
组装2:
namespace NameMatch {
public class Demo { }
public class AssemblyIndicator2 { }
}
现在,创建另一个程序集并引用 Assembly1 和 Assembly2。这是可能的。您甚至可以使用两个相同的类型
NameMatch.Demo
,但不能直接使用:
var demo = new NameMatch.Demo();
// compilation fails:
// The type 'Demo' exists in both 'doubleLoaded1,
// Version=2.1.0.0,
// Culture=neutral, PublicKeyToken=null'
// and 'doubleLoaded2, Version=2.1.0.0, Culture=neutral,
// PublicKeyToken=null'...
以下是如何使用反射检索引用两个程序集的程序集中的类型:
System.Reflection.Assembly assembly1 =
typeof(NameMatch.AssemblyIndicator1).Assembly;
System.Reflection.Assembly assembly2 =
typeof(NameMatch.AssemblyIndicator2).Assembly;
System.Type Type1 = assembly1.GetType("NameMatch.Demo");
System.Type Type2 = assembly2.GetType("NameMatch.Demo");
显然,
bool theSame = Type1 == Type2
给你false
,而Type1.FullName
和Type2.FullName
都返回"NameMatch.Demo"
。
您甚至可以使用反射实例化
Type1
和 Type2
。但类型不同。