Как я могу запустить статический конструктор? - PullRequest
39 голосов
/ 16 апреля 2010

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

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

Редактировать:

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

Ответы [ 10 ]

102 голосов
/ 16 апреля 2010

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

Type type = ...;
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.TypeHandle);
16 голосов
/ 16 апреля 2010

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

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();
4 голосов
/ 16 апреля 2010

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

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

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

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

3 голосов
/ 22 декабря 2017

Расширяя наблюдения Фабио , следующая короткая и полная программа испытаний раскрывает 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} и {Debug, Release} . Я вручную добавил дельта-символ Δ (не излучаемый программой), чтобы подчеркнуть различия между двумя версиями .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
}
1 голос
/ 09 августа 2018

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

type.TypeInitializer.Invoke(null, null);
1 голос
/ 30 декабря 2016

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

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

0 голосов
/ 02 апреля 2019

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

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


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.
}

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

0 голосов
/ 16 апреля 2010

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

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

редактировать

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

0 голосов
/ 16 апреля 2010

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

0 голосов
/ 16 апреля 2010

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

...