Pregunta

Me gustaría ejecutar el constructor estático de una clase (es decir, quiero "carga" de la clase) sin crear una instancia. ¿Cómo lo hago?

Bono pregunta: ¿Hay alguna diferencia entre .NET 4 y versiones anteriores

?

Editar:

  • La clase no es estática.
  • Quiero correr antes de la creación de instancias, ya que toma un tiempo para correr, y me gustaría evitar este retraso en el primer acceso.
  • La Héctor estática inicializa campos private static readonly por lo tanto no se pueden ejecutar en un método en su lugar.
¿Fue útil?

Solución

Las otras respuestas son excelentes, pero si es necesario forzar un constructor de la clase para funcionar sin tener una referencia al tipo (es decir, la reflexión.), Se puede utilizar:

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

Otros consejos

Sólo una referencia de sus campos estáticos. Esto obligará a su código de inicialización estática para funcionar. Por ejemplo:

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);
}

Uso:

// initialize statics
MyClass.TouchMe();

El cctor (constructor estático) será llamada cuando se produce uno de los siguientes;

  1. Se crea una instancia de la clase
  2. Cualquier miembro estático se accede
  3. En cualquier momento antes de eso, si BeforeFieldInit se establece

Si desea explícitamente invocar la cctor, suponiendo que tiene a otros miembros estáticos, simplemente invocar / acceso a ellos.

Si usted no está haciendo nada muy interesante en su cctor, el compilador puede decidir marcarlo BeforeFieldInit, lo que permitirá el CLR la opción de ejecutar el cctor temprano. Esto se explica con más detalle aquí: http: // blogs. msdn.com/davidnotario/archive/2005/02/08/369593.aspx

de Fabio , el siguiente programa de prueba completa y expone los datos sensibles de JIT- TypeAttributes.BeforeFieldInit comportamiento, comparando .NET 3.5 a la versión más reciente (en los últimos 2017) .NET 4.7.1 , y también demuestra los peligros potenciales para las variaciones de tipo de generación dentro de cada versión sí mismo. [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);
    }
};

A continuación se muestra la salida de la consola de ejecutar este programa en todas las combinaciones de {x86, x64} y {debug, release} . Añadí manualmente un Δ símbolo delta (no emitida por el programa) para resaltar las diferencias entre las dos versiones de .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 Δ

Como se ha señalado en la introducción, quizá más interesante que la versión 2.0 / 3.5 contra 4.7 deltas son las diferencias dentro de la versión actual .NET, ya que muestran que, aunque el comportamiento del campo-inicialización hoy en día es más consistente entre x86 y x64 de lo que solía ser, es sigue siendo posible experimentar una diferencia significativa en el campo de tiempo de ejecución de la conducta de inicialización entre su Debug y Release construye hoy.

La semántica dependerá de si le sucede a llamar a un disjuntos o método estático aparentemente no relacionada en la clase o no, por lo que si al hacerlo se introduce un error para su diseño general, es probable que ser bastante misterioso y difícil de rastrear .


Notas
1. El programa anterior utiliza la siguiente función de utilidad para visualizar la corriente CLR Versión:

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
}

constructores estáticos no siempre son llamados cuando se accede a un método estático!

Me di cuenta de que si se llama a un método estático en una clase base, el constructor estático de la super clase no se llama. Este comportamiento inesperado ha mordido muchas veces.

También se puede hacer esto:

type.TypeInitializer.Invoke(null, null);

No estoy exactamente seguro de lo que su caso de uso es para esto, pero puede forzar inicializadores estáticos para funcionar de una manera bastante hacky usando las clases parciales y clases internas:

La idea es utilizar una clase Outter parcial con un campo estático que tiene acceso a otro campo estático en la clase interna. Cuando la clase Outter se inicializa de forma estática, la inicialización de campo estático dará inicio a la inicialización estática de todas las clases internas:


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");
        }
    }
}

Entonces, en algún lugar al principio de su aplicación, sólo tiene que hacer referencia a OutterClass de una manera que resultará en la inicialización estática, como construir una instancia del mismo.

Un ejemplo más realista podría ser ...

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

¿Por qué incluso hacer esto? Tiene un caso de uso muy estrecho. Se elimina la necesidad de tener un único método que inicializa y los registros de todos los servicios. El caso en el que yo personalmente quiero eliminar ese método único de inicialización es para fines de generación de código.

No hay necesidad de hacer esto, el objetivo de un estática constructor es que se ejecuta una vez cuando la clase se inicializa por primera vez en el primer acceso. Si desea ejecutar algo en la demanda, a continuación, considere agregar el código de inicialización en un método público que es llamado por el constructor. A continuación, puede llamar a este método siempre que lo desee. Pero no estoy seguro de ¿Por qué que se quiere hacer esto?

Como han dicho otros, constructores estáticos se ejecutan automáticamente. Si tiene que ser explícita, tal vez debería refactorizar en un método estático que se puede ejecutar de forma explícita?

llamada explícita a un método estático que también, por supuesto, asegurar que el constructor estático había sido ejecutado.

editar

constructores estáticos se ejecuta cuando se hace referencia a los miembros estáticos . Simplemente podría crear un método ficticio llamado initialize el cual no hizo más que asegurar que el marco llama al constructor estático.

El constructor estático se ejecuta automáticamente la primera vez que acceda a la clase. No hay necesidad (o capacidad) a 'correr' por sí mismo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top