关于反射:如何可靠地确定.NET 4.0中的调用程序集

How to reliably determine the calling assembly in .NET 4.0

在.NET 3.5及更低版本中,以下(略为粗糙的示例)效果很好:

装配A:

1
2
3
4
5
6
7
public static class ClassInAssemblyA
{
    public static string GetCallingAssemblyLocation()
    {
        return System.Reflection.Assembly.GetCallingAssembly().GetName(false).CodeBase;
    }
}

装配B:

1
2
3
4
5
6
7
8
9
public class ClassInAssemblyB
{
    public string AssemblyName { get; private set; }

    public ClassInAssemblyB()
    {
        AssemblyName = ClassInAssemblyA.GetCallingAssemblyLocation();
    }
}

程序集C:

1
2
var assemblyName = new ClassInAssemblyB().AssemblyName;
Assert.That(assemblyName.Contains("AssemblyB"));

不幸的是,.NET 4.0 CLR似乎已经过优化,可以将AssemblyA代码内联到AssemblyB中,因此,上述测试在Debug模式下执行时实际上通过了,但在Release模式下失败了。逐步执行时,基本上不可能重现该错误。

一种停止内联的方法是要求调用者每次引用AssemblyA时都添加属性[MethodImpl(MethodImplOptions.NoInlining)]。这是一个笨拙的解决方案,要求调用者知道库的内部工作原理,这不应该是他们的问题,因此我不愿意采用这种方法。

是否有其他方法可以在运行时确定调用程序集的文件名是什么?


除了使用MethodImplOptions.NoInlining,我认为没有其他解决方案。请参阅GetCallingAssembly的文档,其中几乎包含此确切方案。另外,请注意,您需要将属性同时添加到A中的方法和B中的方法中,因为将任何一个内联到其调用程序集中都会导致您看到的行为。


我的理解是,除非您尝试测试自递归调用,否则JIT编译器将始终尊重[Flags(MethodImplOptions.NoInlining)]

这是基于

  • MethodImplOptions的文档(这似乎是特殊情况下的尾递归)
  • 此处讨论的CLI规范的引用:
  • http://bytes.com/topic/c-sharp/answers/509557-race-conditions-c-eventing

    (据我所读,它没有讨论尾递归)