Управление при вызове статического конструктора

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

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

Другие вопросы по тегам