Assembly.GetCallingAssembly() 和静态构造函数?

发布于 2024-07-05 13:58:56 字数 783 浏览 10 评论 0原文

好吧,我刚刚遇到了以下令人惊讶的问题。

由于各种原因,我有一个测试设置,其中TestingAssembly.dll 中的测试类依赖于BaseTestingAssembly.dll 中的TestingBase 类。 TestBase 同时做的一件事是在它自己和调用程序集中查找某个嵌入资源

所以我的 BaseTestingAssembly 包含以下几行...

public class TestBase {    
  private static Assembly _assembly;
  private static Assembly _calling_assembly;

  static TestBase() {
    _assembly = Assembly.GetExecutingAssembly();
    _calling_assembly = Assembly.GetCallingAssembly();
  }
}

静态,因为我认为这些程序集在应用程序的生命周期中是相同的,所以为什么要在每次测试时重新计算它们呢?

然而,当运行这个时,我注意到 _assemble 和 _calling_assemble 都被设置为 BaseTestingAssembly,而不是分别设置为 BaseTestingAssembly 和 TestingAssembly。

将变量设置为非静态并在常规构造函数中初始化它们修复了这个问题,但我很困惑为什么会发生这种情况。 我认为静态构造函数在第一次引用静态成员时运行。 这只能来自我的测试集,它应该是调用者。 有谁知道可能发生了什么?

Ok, so I just ran into the following problem that raised an eyebrow.

For various reasons I have a testing setup where Testing classes in a TestingAssembly.dll depend on the TestingBase class in a BaseTestingAssembly.dll.
One of the things the TestBase does in the meantime is look for a certain embedded resource in its own and the calling assembly

So my BaseTestingAssembly contained the following lines...

public class TestBase {    
  private static Assembly _assembly;
  private static Assembly _calling_assembly;

  static TestBase() {
    _assembly = Assembly.GetExecutingAssembly();
    _calling_assembly = Assembly.GetCallingAssembly();
  }
}

Static since I figured, these assemblies would be the same over the application's lifetime so why bother recalculating them on every single test.

When running this however I noticed that both _assembly and _calling_assembly were being set to BaseTestingAssembly rather than BaseTestingAssembly and TestingAssembly respectively.

Setting the variables to non-static and having them initialized in a regular constructor fixed this but I am confused why this happened to begin this. I thought static constructors run the first time a static member gets referenced. This could only have been from my TestingAssembly which should then have been the caller. Does anyone know what might have happened?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

记忆で 2024-07-12 13:58:57

我认为答案就在 C# 静态构造函数的讨论中。 我最好的猜测是静态构造函数是从意外的上下文中调用的,因为:

用户无法控制何时
静态构造函数在
程序

I think the answer is here in the discussion of C# static constructors. My best guess is that the static constructor is getting called from an unexpected context because:

The user has no control on when the
static constructor is executed in the
program

梦毁影碎の 2024-07-12 13:58:57

Assembly.GetCallingAssembly() 仅返回调用堆栈中第二个条目的程序集。 这很大程度上取决于你的方法/getter/构造函数的调用方式。 这是我在库中所做的,以获取不在我的库中的第一个方法的程序集。 (这甚至适用于静态构造函数。)

private static Assembly GetMyCallingAssembly()
{
  Assembly me = Assembly.GetExecutingAssembly();

  StackTrace st = new StackTrace(false);
  foreach (StackFrame frame in st.GetFrames())
  {
    MethodBase m = frame.GetMethod();
    if (m != null && m.DeclaringType != null && m.DeclaringType.Assembly != me)
      return m.DeclaringType.Assembly;
  }

  return null;
}

Assembly.GetCallingAssembly() simply returns the assembly of the second entry in the call stack. That can very depending upon where how your method/getter/constructor is called. Here is what I did in a library to get the assembly of the first method that is not in my library. (This even works in static constructors.)

private static Assembly GetMyCallingAssembly()
{
  Assembly me = Assembly.GetExecutingAssembly();

  StackTrace st = new StackTrace(false);
  foreach (StackFrame frame in st.GetFrames())
  {
    MethodBase m = frame.GetMethod();
    if (m != null && m.DeclaringType != null && m.DeclaringType.Assembly != me)
      return m.DeclaringType.Assembly;
  }

  return null;
}
反差帅 2024-07-12 13:58:56

静态构造函数由运行时调用,而不是直接由用户代码调用。 您可以通过在构造函数中设置断点然后在调试器中运行来看到这一点。 调用链中紧邻其上方的函数是本机代码。

编辑:静态初始化程序可以通过多种方式在与其他用户代码不同的环境中运行。 其他一些方法是:

  1. 它们隐式地受到保护,免受多线程导致的竞争条件的影响。
  2. 您无法从初始化程序外部捕获异常。

一般来说,最好不要将它们用于任何过于复杂的事情。 您可以使用以下模式实现 single-init:

private static Assembly _assembly;
private static Assembly Assembly {
  get {
    if (_assembly == null) _assembly = Assembly.GetExecutingAssembly();
    return _assembly;
  }
}

private static Assembly _calling_assembly;
private static Assembly CallingAssembly {
  get {
    if (_calling_assembly == null) _calling_assembly = Assembly.GetCallingAssembly();
    return _calling_assembly;
  }
}

如果您期望多线程访问,请添加锁定。

The static constructor is called by the runtime and not directly by user code. You can see this by setting a breakpoint in the constructor and then running in the debugger. The function immediately above it in the call chain is native code.

Edit: There are a lot of ways in which static initializers run in a different environment than other user code. Some other ways are

  1. They're implicitly protected against race conditions resulting from multithreading
  2. You can't catch exceptions from outside the initializer

In general, it's probably best not to use them for anything too sophisticated. You can implement single-init with the following pattern:

private static Assembly _assembly;
private static Assembly Assembly {
  get {
    if (_assembly == null) _assembly = Assembly.GetExecutingAssembly();
    return _assembly;
  }
}

private static Assembly _calling_assembly;
private static Assembly CallingAssembly {
  get {
    if (_calling_assembly == null) _calling_assembly = Assembly.GetCallingAssembly();
    return _calling_assembly;
  }
}

Add locking if you expect multithreaded access.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文