在程序集中搜索类型

6

我遇到了一个问题,代码 Type.GetType(myTypeName) 返回了 null,因为包含该类型的程序集不是当前正在执行的程序集。

我找到的解决方法如下:

var assemblies = AppDomain.CurrentDomain.GetAssemblies();
Type myType = assemblies.SelectMany(a => a.GetTypes())
                        .Single(t => t.FullName == myTypeName);

问题在于这段代码的第一次运行会导致异常"Sequence contains no matching element"。当我再次调用这部分代码时,一切正常,所需类型已加载。
有人能解释这种行为吗?为什么在第一次调用的范围内没有找到所需的程序集/类型?

这可能是竞态条件吗?您是否在加载类型之前请求了它? - Sergey Kalinichenko
是的,这就是原因。有什么最佳实践可以强制加载程序集。 - xwrs
@xwrs 我不确定,但是 Assembly.Load 可以帮助你完成这个任务吗? - Vamsi
@Vamsi Krishna。我已经有了那些程序集的引用。为什么我还需要显式地加载它们? - xwrs
2
@xwrs,关于强制装配加载的问题,SO上已经有一个相关的问题,希望这能帮到你。 - Vamsi
1
你需要强制加载它们,否则它们只会在运行代码中实际需要它们所包含的类型时才被加载。 - Justin Harvey
5个回答

4
您面临的问题是由于AppDomain类的GetAssemblies方法设计引起的 - 根据文档,此方法会获取已加载到该应用程序域的执行上下文中的程序集。
因此,当您的程序第一次无法找到类型时 - 它的程序集显然尚未被应用程序加载。之后 - 当包含所需类型的程序集中的某些功能被使用时 - 程序集已经被加载,同样的代码现在可以找到缺少的类型。
请尝试直接加载程序集。而不是使用:
var assemblies = AppDomain.CurrentDomain.GetAssemblies();

你可以使用:

List<Assembly> assemblies = Assembly.GetEntryAssembly().GetReferencedAssemblies().Select(assembly => Assembly.LoadFrom(assembly.Name)).ToList();

是的,但现在我意识到必须记住并非所有所需程序集都在此时加载。 - xwrs
我不知道我需要哪个程序集以及哪些程序集应该被加载。我不知道哪个程序集包含我需要的类型。 - xwrs
1
请查看我上面的评论,您需要强制加载它们。 - Justin Harvey
@xwrs 请查看更新的答案 - 我已经从Jon Skeet的答案中引用了缺失的部分,由Justing Harvey提供参考。 - Andrii Kalytiiuk

2

可能类型在尚未加载的程序集中。稍后在程序中加载。如果您查看输出窗口,这应该会让您知道何时加载程序集。


是的。程序集加载需要几秒钟(约5-10秒)。有什么最佳实践可以强制加载程序集吗? - xwrs
2
是的,请看这个链接:https://dev59.com/yXE95IYBdhLWcg3wRLst - Justin Harvey

1

另一个答案展示了在运行时获取(可能未加载)Assembly中定义的Type的最佳方法:

var T1 = Type.GetType("System.Web.Configuration.IAssemblyCache, " + 
                      "System.Web, " + 
                      "Version=4.0.0.0, " + 
                      "Culture=neutral, " + 
                      "PublicKeyToken=b03f5f7f11d50a3a");

正如您所见,不幸的是该方法要求您提供完整的TypeAssemblyQualifiedName,并且不适用于我尝试过的任何程序集名称缩写形式。这在某种程度上破坏了我们的主要目的。如果您已经知道有关程序集的这么多细节,那么自己加载它也不会更难:

var T2 = Assembly.Load("System.Web, " + 
                       "Version=4.0.0.0, " + 
                       "Culture=neutral, " + 
                       "PublicKeyToken=b03f5f7f11d50a3a")
                 .GetType("System.Web.Configuration.IAssemblyCache");

尽管这两个示例看起来相似,但它们执行非常不同的代码路径;例如,请注意,后一个版本在Assembly.GetType上调用了一个实例重载,而不是静态调用Type.GetType。因此,第二个版本可能更快或更有效。无论哪种方式,似乎都会到达以下CLR内部方法,并将第二个参数设置为false,这就是为什么两种方法都不费力地为您搜索所需程序集的原因。 private static RuntimeType LoadClrTypeWithPartialBindFallback( String typeName, bool partialFallback); 从这些不便中向前迈出的微小一步是改为自己调用此CLR方法,但将partialFallback参数设置为true。在此模式下,该函数将接受AssemblyQualifiedName的截断版本,并根据需要查找和加载相关程序集:
static Func<String, bool, TypeInfo> LoadClrTypeWithPartialBindFallback =
    typeof(RemotingServices)
    .GetMethod("LoadClrTypeWithPartialBindFallback", (BindingFlags)0x28)
    .CreateDelegate(typeof(Func<String, bool, TypeInfo>))
    as Func<String, bool, TypeInfo>;

// ...

var T3 = LoadClrTypeWithPartialBindFallback(
            "System.Web.Configuration.IAssemblyCache, System.Web",
            true);    //  <-- enables searching for the assembly

这个示例可以正常工作,并且还支持像之前的示例一样指定完整的AssemblyQualifiedName。这是一个小改进,但它仍然不是完全未经限定的命名空间搜索,因为你仍然需要指定程序集的短名称,即使它可能可以从类型名称本身中推断出命名空间。

0
顺便说一下,如果你知道包含该类型的程序集的FullName(或者包含TypeForwardedToAttribute的程序集),那么你可以使用Type.GetType方法。具体来说,就是使用Type.GetType(Assembly.CreateQualifiedName(assembly.FullName, myTypeName)),它的结构大致如下:
Type.GetType("Some.Complete.Namespace.myTypeName, Some.Assembly.Name, Version=1.2.3.4, Culture=neutral, PublicKeyToken=ffffffffffffffff");

如果框架能够解析程序集的位置,将会加载此程序集,如果尚未加载。

这是我的示例 LinqPad 查询,确认括号内的 TypeForwardedToAttribute 备注:

var u = (from a in AppDomain.CurrentDomain.GetAssemblies()
         let t = a.GetType("System.Lazy`2")
         where t != null
         select t).FirstOrDefault();
(u?.AssemblyQualifiedName).Dump();
u = Type.GetType("System.Lazy`2, System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
(u?.AssemblyQualifiedName).Dump();

输出:

null
System.Lazy`2,System.ComponentModel.Composition,版本=4.0.0.0,文化=中性,PublicKeyToken=b77a5c561934e089


0

如果你排除了mscorlib程序集,你可以尝试这个:

  var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(asb=>!asb.FullName.StartsWith("mscorlib")).ToList();

Type myType = assemblies.SelectMany(a => a.GetTypes())
                        .Single(t => t.FullName == myTypeName);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接