我有一个包含32个项目的Visual Studio 2023解决方案.这些项目相互广泛参考.其中31个项目是类库,而最后一个项目是一个控制台应用程序,每当我想测试我的库中的一个函数时,就会使用它,所以我不必每次都创建新的临时函数.即使在这个问题之前,Visual Studio的即时窗口对我来说也从未起过作用.所有的项目都是用C#11编写的,并使用.NET 7.0.许多项目都引用了NuGet Package System.Collections.Immutable
.
我试图写一个Crawler,就像Web Crawler一样,但针对的是类型.它将迭代类型的所有静态/非静态成员,添加它们的参数类型,并将类型(如果存在)返回给HashSet<Type>
.然后,它将递归地为添加的每个类型执行相同的操作.它被设计成每次只迭代一次.
它通过创建List<Type>
来做到这一点,每次向HashSet<Type>
添加一个尚不存在的类型时,它都会将其添加到列表中.在列表完成之后,它将递归地为列表中的每种类型执行相同的操作.如果类型是泛型参数或包含在其泛型参数中(如果存在),则它将为substituted with its definition.还有一些函数用于覆盖指针、引用和array.
我在object上运行了这个功能,它运行得很好.我使用C#的S typeof(?)
方法获得了对象的类型.然而,然后我在我的一个项目中的静态类上运行了该函数,它抛出了一个异常.
在这一点上,值得一提的是与此相关的项目.我有一个控制台应用程序项目(Test
),其中我正在执行第二个项目(Brendan.IC.Typed.Execution
)中的爬行器,该爬行器应该爬行通过第三个项目(Brendan.IC.Typed
)中的静态类.后一个项目引用了System.Collections.Immutable
个.System.Collections.Immutable
很可能与此无关,但我认为所有事情都值得一提,因为我完全不知道问题出在哪里.
唯一的例外是System.TypeLoadException
.
System.TypeLoadException:‘无法从程序集’Brendan.IC.Typed,Version=1.0.0.0,区域性=中立,PublicKeyToken=NULL‘加载类型’Brendan.IC.Typed.TypedEvolutionLookup`1‘.’
我调用爬行器的静态类是Brendan.IC.Typed.TypedEvolution
.它的一个静态方法包含类型为Brendan.IC.Typed.TypedEvolutionLookup<>
的参数.爬行者就是这样找到它的.然后,它无法加载它.异常是在MethodInfo.GetParameters()
函数中引发的.
起初,我以为这是我的爬虫功能出现了一些问题.我清理并重新生成了解决方案,这是我处理错误的正常第一步,但没有效果.我在Debug
和Release
模式下多次重新编译了解决方案,但都没有效果.我判断了代码中是否有任何可能影响类型加载的内容.我什么也没找到.最后,我决定在我的 top-level 中加载有问题的类型.它抛出了一个异常,与之前的异常相同.
System.TypeLoadException:‘无法从程序集’Brendan.IC.Typed,Version=1.0.0.0,区域性=中性,‘CodeInterpretation.Typed.TypedEvolutionLookup`1’=NULL‘加载类型密钥.’
以下是我的最高层声明:
using Brendan.IC.Typed;
using Brendan.IC.Typed.Execution;
using static System.Console;
Type bigType = typeof(TypedEvolutionLookup<int>);
Crawler.Crawl(bigType);
ReadKey(true);
我把它简化为:
using Brendan.IC.Typed;
using static System.Console;
Type bigType = typeof(TypedEvolutionLookup<int>);
ReadKey(true);
就在这时,事情开始变得奇怪起来.真的很奇怪.
首先,我注意到没有与异常相关联的行号或调用堆栈.
Select 显示调用堆栈后:
这是我的全屏:
通常,当我收到异常时,我可以在我怀疑的行前添加一个WriteLine(),它将在程序因异常而终止之前打印到控制台.我这样做了,并在同一行上添加了断点,打算逐步执行该程序.最后,我向泛型类型定义添加了一个泛型参数.但是程序甚至没有命中断点!它也没有打印到控制台上!
新代码:
using Brendan.IC.Typed;
using static System.Console;
WriteLine("hi");
Type bigType = typeof(TypedEvolutionLookup<int>);
ReadKey(true);
屏幕截图:
我也把它设为释放模式.不出所料,它没有抛出错误,因为它优化了不必要的赋值.显然,它也没有触及断点.
我go 掉了第四行,然后它就可以正常工作了.我把它加回go ,它坏了.我试着从Brendan.IC.Typed
的组装中得到一个不同的型号.它运行得很好.我用另一个ReadKey(true)
语句替换了WriteLine()
语句.它坏了.
using Brendan.IC.Typed;
using static System.Console;
ReadKey(true);
Type bigType = typeof(TypedEvolutionLookup<int>);
ReadKey(true);
我go 掉了泛型参数.没有变化:
让我担心的是,我认为错误不是在执行typeof
语句时发生的,而是在IL的编译器中发生的!错误是在我编译它和执行 top-level 之间抛出的!我能想到的只有一件事符合这些约束,并且取决于typeof
行的存在,而且它是IL运行时编译器本身!然而,C#到IL编译器可以很容易地找到该类型!
并且该错误不会在程序集中的任何其他类型中发生!不幸的是,TypedEvolutionLookup<>
struct 是Brendan.IC.Typed
程序集中唯一的泛型类型.但是,它不是唯一的只读 struct .不过,我已经测试了其他一些只读 struct ,它们没有相同的问题.出于某种原因,IL编译器似乎挑出了这一特定类型,并且,无论是否依赖泛型,这种情况都不应该发生.
我已经多次重启了我的计算机,以防有一些我不知道的秘密RAM类型缓存.我已经多次清理和重新构建了解决方案,以防出现什么奇怪的情况.我已经多次删除了我所有项目的输出目录,以防一些过go 的版本与现在的版本发生冲突.
(我的所有类库都编译到相同的输出目录;但是,控制台应用程序编译到自己的目录,以避免混淆.)
没有任何变化.我不明白这一点.我想要这座建筑的System.Type
英镑.为什么IL编译器要挑出这个特定的对象?它没有时髦的属性,除了它的方法的参数中偶尔有[MaybeNullWhen(false)]
.
为什么会发生这种情况,我又该如何避免呢?
下面是有问题的 struct 的代码:
public readonly struct TypedEvolutionLookup<TCommand>
where TCommand : struct
{
public TCommand? Command { get; }
public ImmutableArray<KeyValuePair<Type, TypedEvolutionLookup<TCommand>>> OtherCommands { get; }
public int Count { get; }
}
为简洁起见,我删除了构造函数,但它仍然生成TypeLoadException
.
正如用户Dave Cousineau和Renat推测的那样,类型确实包含构造的泛型类型,并将其本身作为参数.如果有必要,我会补充这一点作为回答.