Assembly.GetCallingAssembly() и статические конструкторы?

Хорошо, я столкнулся со следующей проблемой, которая подняла бровь.

По разным причинам у меня есть настройка тестирования, при которой классы тестирования в TestingAssembly.dll зависят от класса TestingBase в BaseTestingAssembly.dll. В то же время TestBase ищет определенный встроенный ресурс самостоятельно и вызывающую сборку.

Итак, моя BaseTestingAssembly содержала следующие строки...

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

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

С тех пор, как я понял, эти сборки будут одинаковыми на протяжении всего жизненного цикла приложения, поэтому зачем пересчитывать их при каждом тесте.

Однако при выполнении этого я заметил, что для _assembly и _calling_assembly были установлены значения BaseTestingAssembly, а не BaseTestingAssembly и TestingAssembly соответственно.

Установка переменных на нестатические и инициализация их в обычном конструкторе исправила это, но я запутался, почему это случилось, чтобы начать это. Я думал, что статические конструкторы запускаются при первом обращении к статическому члену. Это могло быть только из моей TestingAssembly, которая должна была быть вызывающей. Кто-нибудь знает, что могло случиться?

3 ответа

Решение

Статический конструктор вызывается средой выполнения, а не напрямую кодом пользователя. Это можно увидеть, установив точку останова в конструкторе, а затем запустив ее в отладчике. Функция непосредственно над ней в цепочке вызовов - это собственный код.

Изменить: Есть много способов, в которых статические инициализаторы работают в другой среде, чем другой пользовательский код. Некоторые другие способы

  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;
  }
}

Добавьте блокировку, если вы ожидаете многопоточный доступ.

Я думаю, что ответ здесь в обсуждении статических конструкторов C#. Я думаю, что статический конструктор вызывается из неожиданного контекста, потому что:

Пользователь не может контролировать, когда статический конструктор выполняется в программе.

Assembly.GetCallingAssembly() просто возвращает сборку второй записи в стеке вызовов. Это может очень зависеть от того, как вызывается ваш метод / метод получения / конструктор. Вот что я сделал в библиотеке, чтобы получить сборку первого метода, которого нет в моей библиотеке. (Это даже работает в статических конструкторах.)

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;
}
Другие вопросы по тегам