Assembly.GetCallingAssembly() и статические конструкторы?
-
02-07-2019 - |
Вопрос
Итак, я только что столкнулся со следующей проблемой, которая вызвала удивление.
По разным причинам у меня есть настройка тестирования, в которой классы тестирования в 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, которая тогда должна была быть вызывающей стороной.Кто-нибудь знает, что могло случиться?
Решение
Статический конструктор вызывается средой выполнения, а не напрямую пользовательским кодом.Вы можете увидеть это, установив точку останова в конструкторе и затем запустив отладчик.Функция, расположенная непосредственно над ней в цепочке вызовов, представляет собой машинный код.
Редактировать: Существует множество способов запуска статических инициализаторов в среде, отличной от другого пользовательского кода.Некоторые другие способы
- Они неявно защищены от условий гонки, возникающих в результате многопоточности.
- Вы не можете перехватывать исключения вне инициализатора.
В общем, вероятно, лучше не использовать их для чего-то слишком сложного.Вы можете реализовать одиночную инициализацию по следующему шаблону:
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;
}