Управление при вызове статического конструктора
В статическом конструкторе моего пользовательского атрибута я ищу в загруженной сборке все классы, украшенные моим атрибутом, и выполняю над ними некоторые действия.
Я хотел бы, чтобы статический конструктор вызывался как можно скорее во время выполнения, предпочтительно перед выполнением static void Main()
точка входа.
В настоящее время он вызывается только после того, как я вызову атрибут. Я мог бы сделать такой вызов в другом месте моей программы, но в идеале функциональность атрибута была бы автономной.
Ища ответы, я прочитал это на MSDN:
Пользователь не может контролировать, когда статический конструктор выполняется в программе.
Но, безусловно, есть хитрый, хитрый или вредный обходной путь, чтобы статический конструктор назывался как можно скорее. Возможно, можно использовать атрибут, отражение или какую-то другую магию. Это можно сделать?
Поскольку люди, несомненно, скажут мне, что нет веских причин делать то, что я прошу, я представляю свою цель и мой код: я пытаюсь использовать атрибуты для декларативной настройки фабрики db4o. Если статический конструктор моего атрибута вызывается после того, как я уже установил соединение, то это не имеет никакого эффекта и бесполезно. Поэтому он должен быть вызван до того, как моя программа получит возможность установить такое соединение.
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
sealed public class CascadeOnUpdateAttribute : Attribute
{
public bool Flag { get; private set; }
public CascadeOnUpdateAttribute() : this(true) { }
public CascadeOnUpdateAttribute(bool flag)
{
Flag = flag;
}
static CascadeOnUpdateAttribute()
{
var targets = from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
from attribute in type.GetCustomAttributes(typeof(CascadeOnUpdateAttribute), false).Cast<CascadeOnUpdateAttribute>()
select new { Type = type, Cascade = attribute.Flag };
foreach (var target in targets)
{
Db4oFactory.Configure().ObjectClass(target.Type).CascadeOnUpdate(target.Cascade);
}
}
}
Обновить:
В итоге я использовал абстрактный атрибут со статическим методом. Таким образом, я могу получить столько атрибутов, сколько захочу, и все они будут применены к указанной конфигурации, вызывая этот единственный метод.
public abstract class Db4oAttribute : Attribute
{
public abstract void Configure(IConfiguration config, Type type);
public static void ApplyAttributes(IConfiguration config)
{
var targets = from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
from attribute in type.GetCustomAttributes(typeof(Db4oAttribute), false).Cast<Db4oAttribute>()
select new { Type = type, Attribute = attribute };
foreach (var target in targets)
{
target.Attribute.Configure(config, target.Type);
}
}
}
И сайт вызова:
Db4oAttribute.ApplyAttributes(Db4oFactory.Configure());
_db = Db4oFactory.OpenFile("Test.db4o");
4 ответа
Как говорит Марк, я бы сделал это явно в Main
на твоем месте.
Вы можете вызвать инициализатор типа для типа явно, используя Type.TypeInitializer
собственность и вызывая ее. Однако это приведет к его повторному запуску, даже если он уже запущен, что может привести к неожиданным результатам.
Я бы лично переместил этот код из статического инициализатора полностью. Это код конфигурации - почему бы просто не сделать его статическим методом, который вы можете вызвать явно? Я даже не уверен, что это будет в самом классе атрибутов, но, по крайней мере, вызов явно:
CascadeOnUpdateAttribute.ConfigureDb4oFactories();
это понятнее, чем вызов фиктивного метода или принудительная инициализация типа каким-либо другим способом, просто чтобы получить побочный эффект.
Если вы хотите, чтобы вызывался статический конструктор, добавьте в тип фиктивный метод и просто вызовите его в начале кода (Main
так далее); если это тривиальный / пустой метод, вы можете пометить его без встраивания и т. д.
class SomeType {
static SomeType() {
Console.WriteLine("SomeType.cctor");
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Init() { }
}
static class Program {
static void Main() {
SomeType.Init();
Console.WriteLine("hi");
}
}
Вы можете использовать рефлексию для вызова статического конструктора, но я не рекомендую это делать; если вы используете рефлексию, вы можете вызывать.cctor несколько раз, и это никогда не бывает хорошо...
Вы можете избежать статического фиктивного метода, вызвав
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(CascadeOnUpdateAttribute).TypeHandle)
Я думаю, что использование статического конструктора пахнет; Я хотел бы рассмотреть возможность рефакторинга вашего кода для контроля доступа к фабрике db4o, чтобы вам не нужно было его использовать.