una - herencia de constructores c#
¿Cuál es la mejor manera de garantizar que se llame al constructor estático de una clase base? (6)
Casi siempre me arrepiento de confiar en algo como esto. Los métodos y clases estáticos pueden limitarte más adelante. Si quisieras codificar algún comportamiento especial para tu clase de Tipo más tarde, estarías encasillado.
Entonces, aquí hay una ligera variación en su enfoque. Es un poco más código, pero le permitirá tener un tipo personalizado definido más adelante que le permite hacer cosas personalizadas.
abstract class TypeBase
{
private static bool _initialized;
protected static void Initialize()
{
if (!_initialized)
{
Type<int>.Instance = new Type<int> {Name = "int"};
Type<long>.Instance = new Type<long> {Name = "long"};
Type<double>.Instance = new Type<double> {Name = "double"};
_initialized = true;
}
}
}
class Type<T> : TypeBase
{
private static Type<T> _instance;
public static Type<T> Instance
{
get
{
Initialize();
return _instance;
}
internal set { _instance = value; }
}
public string Name { get; internal set; }
}
Luego, más adelante, cuando agregue un método virtual a Type y desee una implementación especial para Type, podrá implementarlo de la siguiente manera:
class TypeInt : Type<int>
{
public override string Foo()
{
return "Int Fooooo";
}
}
Y luego conectarlo cambiando
protected static void Initialize()
{
if (!_initialized)
{
Type<int>.Instance = new TypeInt {Name = "int"};
Type<long>.Instance = new Type<long> {Name = "long"};
Type<double>.Instance = new Type<double> {Name = "double"};
_initialized = true;
}
}
Mi consejo sería evitar los constructores estáticos, es fácil de hacer. También evite las clases estáticas y, cuando sea posible, los miembros estáticos. No digo que nunca, solo con moderación. Prefiere un singleton de una clase a una estática.
La documentación sobre constructores estáticos en C # dice:
Un constructor estático se usa para inicializar cualquier información estática, o para realizar una acción particular que necesita realizarse una sola vez. Se llama automáticamente antes de que se cree la primera instancia o se hace referencia a cualquier miembro estático .
Esa última parte (sobre cuándo se llama automáticamente) me lanzó a un bucle; hasta que leí esa parte, pensé que simplemente accediendo a una clase de alguna manera , podría estar seguro de que se había llamado al constructor estático de la clase base. Probar y examinar la documentación ha revelado que este no es el caso; parece que no se garantiza que se ejecute el constructor estático para una clase base hasta que se acceda específicamente a un miembro de esa clase base .
Ahora, supongo que en la mayoría de los casos cuando se trata de una clase derivada, se construiría una instancia y esto constituiría una instancia de la clase base que se está creando, por lo que se llamaría al constructor estático. Pero si solo estoy tratando con miembros estáticos de la clase derivada , ¿entonces qué?
Para hacer esto un poco más concreto, pensé que el siguiente código funcionaría:
abstract class TypeBase
{
static TypeBase()
{
Type<int>.Name = "int";
Type<long>.Name = "long";
Type<double>.Name = "double";
}
}
class Type<T> : TypeBase
{
public static string Name { get; internal set; }
}
class Program
{
Console.WriteLine(Type<int>.Name);
}
Supuse que acceder a la clase Type<T>
invocaría automáticamente el constructor estático para TypeBase
; pero este no parece ser el caso. Type<int>.Name
es null
, y el código de arriba muestra la cadena vacía.
Además de crear un miembro ficticio (como un método Initialize()
estático que no hace nada), ¿hay una forma mejor de garantizar que se invoque el constructor estático de un tipo de base antes de usar cualquiera de sus tipos derivados?
Si no, entonces ... ¡es un miembro ficticio!
Como otros han notado, su análisis es correcto. La especificación se implementa literalmente aquí; dado que no se ha invocado ningún miembro de la clase base y no se ha creado ninguna instancia, no se llama al constructor estático de la clase base. Puedo ver cómo eso podría ser sorprendente, pero es una implementación estricta y correcta de la especificación.
No tengo ningún consejo para ti aparte de "si te duele cuando haces eso, no hagas eso". Solo quería señalar que el caso contrario también puede morderte:
class Program
{
static void Main(string[] args)
{
D.M();
}
}
class B
{
static B() { Console.WriteLine("B"); }
public static void M() {}
}
class D: B
{
static D() { Console.WriteLine("D"); }
}
Esto imprime "B" a pesar del hecho de que "un miembro de D" ha sido invocado. M es un miembro de D únicamente por herencia; el CLR no tiene forma de distinguir si BM fue invocado "a través de D" o "a través de B".
En todas mis pruebas, solo pude obtener una llamada a un miembro ficticio en la base para hacer que la base llamara a su constructor estático como se ilustra:
class Base
{
static Base()
{
Console.WriteLine("Base static constructor called.");
}
internal static void Initialize() { }
}
class Derived : Base
{
static Derived()
{
Initialize(); //Removing this will cause the Base static constructor not to be executed.
Console.WriteLine("Derived static constructor called.");
}
public static void DoStaticStuff()
{
Console.WriteLine("Doing static stuff.");
}
}
class Program
{
static void Main(string[] args)
{
Derived.DoStaticStuff();
}
}
La otra opción era incluir un miembro estático de solo lectura en el tipo derivado que hiciera lo siguiente:
private static readonly Base myBase = new Base();
Sin embargo, esto se siente como un hack (aunque también lo hace el miembro ficticio) solo para llamar al constructor estático base.
Las reglas aquí son muy complejas , y entre CLR 2.0 y CLR 4.0 realmente cambiaron de manera sutil e interesante , que la OMI hace que los enfoques más "inteligentes" sean frágiles entre las versiones de CLR. Un método Initialize()
también puede no hacer el trabajo en CLR 4.0 si no toca los campos.
Buscaría un diseño alternativo, o tal vez usaría una inicialización diferida regular en su tipo (es decir, verifique un poco o una referencia (contra null
) para ver si se ha realizado).
Puede llamar explícitamente al constructor estático, por lo que no tendrá que crear ningún método para la inicialización:
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof (TypeBase).TypeHandle);
Puede llamarlo en constructor estático de clase derivada.
Solo una idea, puedes hacer algo como esto:
abstract class TypeBase
{
static TypeBase()
{
Type<int>.Name = "int";
Type<long>.Name = "long";
Type<double>.Name = "double";
}
}
class Type<T> : TypeBase
{
static Type()
{
new Type<object>();
}
public static string Name { get; internal set; }
}
class Program
{
Console.WriteLine(Type<int>.Name);
}