Подтвердить что ты не робот

Как запустить статический конструктор?

Я хотел бы выполнить статический конструктор класса (т.е. хочу "загрузить" класс), не создавая экземпляр. Как это сделать?

Бонусный вопрос: существуют ли различия между .NET 4 и более ранними версиями?

Edit:

  • Класс не является статическим.
  • Я хочу запустить его перед созданием экземпляров, потому что для запуска требуется некоторое время, и я бы хотел избежать этой задержки при первом доступе.
  • Статический ctor инициализирует поля private static readonly, поэтому не может быть запущен в методе.
4b9b3361

Ответ 1

Другие ответы превосходны, но если вам нужно заставить конструктор классов работать без ссылки на тип (т.е. отражение), вы можете использовать:

Type type = ...;
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.TypeHandle);

Ответ 2

Просто назовите одно из ваших статических полей. Это заставит ваш статический код инициализации работать. Например:

public class MyClass
{
    private static readonly int someStaticField;

    static MyClass() => someStaticField = 1;

    // any no-op method call accepting your object will do fine
    public static void TouchMe() => GC.KeepAlive(someStaticField);
}

Использование:

// initialize statics
MyClass.TouchMe();

Ответ 3

Конец (статический конструктор) будет вызываться всякий раз, когда возникает одно из следующих событий:

  • Вы создаете экземпляр класса
  • Доступ к любому статическому члену
  • В любое время до этого, если BeforeFieldInit установлен

Если вы хотите явно вызвать cctor, предполагая, что у вас есть другие статические члены, просто вызывайте/обращайтесь к ним.

Если вы не делаете что-либо очень интересное в своем cctor, компилятор может решить пометить его BeforeFieldInit, что даст CLR возможность выполнить cctor раньше. Это объясняется более подробно здесь: http://blogs.msdn.com/davidnotario/archive/2005/02/08/369593.aspx

Ответ 4

Расширение Fábio наблюдений, следующая краткая и полная тестовая программа раскрывает JIT-чувствительные данные TypeAttributes.BeforeFieldInit, сравнивая .NET 3.5 с последней версией (по состоянию на конец 2017 года) .NET 4.7.1, а также демонстрирует потенциальную опасность для вариантов типа сборки внутри каждой версии. [1]

using System;
using System.Diagnostics;

class MyClass
{
    public static Object _field = Program.init();

    public static void TouchMe() { }
};

class Program
{
    static String methodcall, fieldinit;

    public static Object init() { return fieldinit = "fieldinit"; }

    static void Main(String[] args)
    {
        if (args.Length != 0)
        {
            methodcall = "TouchMe";
            MyClass.TouchMe();
        }
        Console.WriteLine("{0,18}  {1,7}  {2}", clrver(), methodcall, fieldinit);
    }
};

Ниже показан вывод консоли из этой программы во всех комбинациях {x86, x64} и {Отладка, выпуск}. Я вручную добавил символ треугольника Δ (не выпущенный программой), чтобы выделить различия между двумя версиями .NET.

.NET 2.0/3.5

2.0.50727.8825 x86 Debug
2.0.50727.8825 x86 Debug TouchMe fieldinit
2.0.50727.8825 x86 Release fieldinit
2.0.50727.8825 x86 Release TouchMe fieldinit
2.0.50727.8825 x64 Debug
2.0.50727.8825 x64 Debug TouchMe fieldinit
2.0.50727.8825 x64 Release
2.0.50727.8825 x64 Release TouchMe fieldinit

.NET 4.7.1

4.7.2556.0 x86 Debug
4.7.2556.0 x86 Debug TouchMe fieldinit
4.7.2556.0 x86 Release Δ
4.7.2556.0 x86 Release TouchMe Δ
4.7.2556.0 x64 Debug
4.7.2556.0 x64 Debug TouchMe fieldinit
4.7.2556.0 x64 Release
4.7.2556.0 x64 Release TouchMe Δ

Как отмечено во вступлении, возможно, более интересным, чем версии 2.0/ 3,5 по сравнению с 4.7 - это различия в текущей .NET. версии, поскольку они показывают, что хотя поведение инициализации поля в настоящее время более согласовано между x86 и x64, чем это было раньше, все же можно ощутить существенную разницу в поведении инициализации поля времени выполнения между вашими Debug и Release создается сегодня.

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


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

static String clrver()
{
    var s = typeof(Uri).Assembly.Location;
    return FileVersionInfo.GetVersionInfo(s).ProductVersion.PadRight(14) +
        (IntPtr.Size == 4 ? " x86 " : " x64 ") +
#if DEBUG
        "Debug  ";
#else
        "Release";
#endif
}

Ответ 5

Статические конструкторы НЕ всегда вызываются при доступе к статическому методу!

Я заметил, что если вы вызываете статический метод в базовом классе, статический конструктор суперкласса НЕ вызывается. Это неожиданное поведение укусило много раз.

Ответ 6

Также вы можете сделать это:

type.TypeInitializer.Invoke(null, null);

Ответ 7

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

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


public partial class OutterClass
{
    // When OutterClass is initialized, this will force InnerClass1 to be initialized.
    private static int _innerClass1Touched = InnerClass1.TouchMe;

    public static class InnerClass1
    {
        public static int TouchMe = 0;

        static InnerClass1()
        {
            Console.WriteLine("InnerClassInitialized");
        }
    }
}

public partial class OutterClass
{
    // When OutterClass is initialized, this will force InnerClass2 to be initialized.
    private static int _innerClass2Touched = InnerClass2.TouchMe;

    public static class InnerClass2
    {
        public static int TouchMe = 0;

        static InnerClass2()
        {
            Console.WriteLine("InnerClass2Initialized");
        }
    }
}

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

Более реалистичным примером может быть...

public interface IService
{
    void SayHello();
}

public partial class ServiceRegistry
{
    private static List<Func<IService>> _serviceFactories;

    private static void RegisterServiceFactory(Func<IService> serviceFactory)
    {
        // This has to be lazily initialized, because the order of static initialization
        //  isn't defined. RegisterServiceFactory could be called before _serviceFactories
        //  is initialized.
        if (_serviceFactories == null)
            _serviceFactories = new List<Func<IService>>();

        _serviceFactories.Add(serviceFactory);
    }

    public List<IService> Services { get; private set; }

    public ServiceRegistry()
    {
        Services = new List<IService>();

        foreach (var serviceFactory in _serviceFactories)
        {
            Services.Add(serviceFactory());
        }
    }
}


// In another file (ServiceOne.cs):
public class ServiceOne : IService
{
    void IService.SayHello()
    {
        Console.WriteLine("Hello from ServiceOne");
    }
}

public partial class ServiceRegistry
{
    // When OutterClass is initialized, this will force InnerClass1 to be initialized.
    private static int _serviceOneRegistryInitializer = ServiceOneRegistry.Initialize;

    private static class ServiceOneRegistry
    {
        public static int Initialize = 0;

        static ServiceOneRegistry()
        {
            ServiceRegistry.RegisterServiceFactory(() => new ServiceOne());
        }
    }
}

// In another file (ServiceTwo.cs):
public class ServiceTwo : IService
{
    void IService.SayHello()
    {
        Console.WriteLine("Hello from ServiceTwo");
    }
}

public partial class ServiceRegistry
{
    // When OutterClass is initialized, this will force InnerClass1 to be initialized.
    private static int _serviceTwoRegistryInitializer = ServiceTwoRegistry.Initialize;

    private static class ServiceTwoRegistry
    {
        public static int Initialize = 0;

        static ServiceTwoRegistry()
        {
            ServiceRegistry.RegisterServiceFactory(() => new ServiceTwo());
        }
    }
}

static void Main(string[] args)
{
    ServiceRegistry registry = new ServiceRegistry();
    foreach (var service in registry.Services)
    {
        serivce.SayHello();
    }

    // Output will be:
    // Hello from ServiceOne
    // Hello from ServiceTwo

    // No guarantee on order.
}

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

Ответ 8

Нет необходимости делать это, вся статическая конструктор статический конструктор заключается в том, что он запускается один раз, когда класс сначала инициализируется в первый доступ. Если вы хотите запустить что-то по требованию, подумайте о добавлении кода инициализации в открытый метод, который вызывается конструктором. Вы можете вызвать этот метод, когда захотите. Но я не уверен, почему вы хотели бы это сделать?

Ответ 9

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

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

изменить

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

Ответ 10

Статический конструктор запускается автоматически при первом доступе к классу. Нет необходимости (или возможности) "запускать" его самостоятельно.