Вопрос

Итак, я только что столкнулся со следующей проблемой, которая вызвала удивление.

По разным причинам у меня есть настройка тестирования, в которой классы тестирования в 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, которая тогда должна была быть вызывающей стороной.Кто-нибудь знает, что могло случиться?

Это было полезно?

Решение

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

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

  1. Они неявно защищены от условий гонки, возникающих в результате многопоточности.
  2. Вы не можете перехватывать исключения вне инициализатора.

В общем, вероятно, лучше не использовать их для чего-то слишком сложного.Вы можете реализовать одиночную инициализацию по следующему шаблону:

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;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top