39

C# 中关于静态构造函数的文档说:

静态构造函数用于初始化任何静态数据,或执行只需要执行一次的特定操作。在创建第一个实例或引用任何静态成员之前自动调用它。

最后一部分(关于它何时被自动调用)让我陷入了循环;在阅读那部分之前,我认为通过简单地以任何方式访问一个类,我可以确定它的基类的静态构造函数已被调用。测试和检查文档表明情况并非如此;似乎在专门访问该基类的成员之前,不能保证基类的静态构造函数运行。

现在,我想在大多数情况下,当您处理派生类时,您会构造一个实例,这将构成正在创建的基类的实例,因此将调用静态构造函数。但是如果我只处理派生类的静态成员,那又如何呢?

为了更具体一点,我认为下面的代码可以工作:

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

我假设访问Type<T>该类会自动调用静态构造函数TypeBase;但情况似乎并非如此。Type<int>.Namenull,上面的代码输出空字符串。

除了创建一些虚拟成员(如Initialize()什么都不做的静态方法)之外,还有更好的方法来确保在使用任何派生类型之前调用基类型的静态构造函数吗?

如果不是,那么......它是虚拟成员!

4

6 回答 6

28

您可以显式调用静态构造函数,因此您不必创建任何初始化方法:

System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof (TypeBase).TypeHandle);

您可以在派生类的静态构造函数中调用它。

于 2011-01-11T00:03:53.497 回答
20

As others have noted, your analysis is correct. The spec is implemented quite literally here; since no member of the base class has been invoked and no instance has been created, the static constructor of the base class is not called. I can see how that might be surprising, but it is a strict and correct implementation of the spec.

I don't have any advice for you other than "if it hurts when you do that, don't do that." I just wanted to point out that the opposite case can also bite you:

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

This prints "B" despite the fact that "a member of D" has been invoked. M is a member of D solely by inheritance; the CLR has no way of distinguishing whether B.M was invoked "through D" or "through B".

于 2011-01-11T00:33:22.450 回答
14

这里的规则非常复杂,在 CLR 2.0 和 CLR 4.0 之间,它们实际上以微妙而有趣的方式发生了变化,IMO 使大多数“聪明”的方法在 CLR 版本之间变得脆弱。如果Initialize()方法不涉及字段,它也可能无法在 CLR 4.0 中完成工作。

我会寻找一种替代设计,或者可能在您的类型中使用常规的延迟初始化(即检查一点或参考(反对null)以查看它是否已完成)。

于 2011-01-10T23:15:25.100 回答
3

我几乎总是后悔依赖这样的东西。静态方法和类可能会在以后限制您。如果您想稍后为您的 Type 类编写一些特殊行为,您将被装箱。

因此,您的方法略有不同。它的代码有点多,但它允许您稍后定义一个自定义类型,让您可以执行自定义操作。

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

然后稍后当您向 Type 添加一个虚拟方法并想要一个特殊的 Type 实现时,您可以这样实现:

class TypeInt : Type<int>
{
    public override string Foo()
    {
        return "Int Fooooo";
    }
}

然后通过改变来连接它

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

我的建议是避免使用静态构造函数——这很容易做到。还要避免使用静态类和可能的静态成员。我不是说从不,只是有节制地。更喜欢类的单例而不是静态。

于 2011-01-11T02:50:20.463 回答
3

在我的所有测试中,我只能调用基础上的一个虚拟成员,以使基础调用其静态构造函数,如图所示:

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

另一个选项是在派生类型中包含一个静态只读成员,该成员执行以下操作:

private static readonly Base myBase = new Base();

然而,这感觉就像一个 hack(虽然虚拟成员也是如此)只是为了调用基本的静态构造函数。

于 2011-01-10T23:13:06.910 回答
0

只是一个想法,你可以这样做:

    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);
    }
于 2011-01-11T00:03:39.333 回答