.NET中的静态类初始化

时间:2015-02-12 06:20:29

标签: .net

我知道我们不会初始化静态类。我们可以按类名使用它们。但它必须在静态内存中,以便我们可以使用它们。什么时候静态类放在静态内存中?是运行时还是延迟加载?

1 个答案:

答案 0 :(得分:3)

我怀疑你认为它很懒惰。我不知道你对“静态记忆”是什么意思,但我认为这是一个非常可行的问题,关于静态成员的运作方式以及他们如何使用记忆。静态成员将被放置在“常规内存”中,就像任何实例成员一样。

静态实例在加载时被放置到堆上(尽管它是一种不同于实例的堆,称为High Frequency Heap),但在使用之前不会被初始化。根据您拥有的静态成员(以及它们的类型,以及这些类型是引用类型还是值类型),这可能是一个大的或小的交易。

例如,如果您有一个包含200个元素的字节数组,那么只有在您使用它时才会加载(少于它最初会保留的null)。但是如果你有200个字节的元素,每个元素都将在开头放入内存。

换句话说,在使用之前,ClassA占用的内存少于ClassB,但在使用过程中,它们会相似。当然,情况并不完全相同,但你可以想象我想要做的一点。

static class ClassA
{
    public static byte[] bytes = new byte[] { 1, 12 };
}

static class ClassB
{
    public static byte ByteA = 1;
    public static byte ByteB = 12;
}

这是因为在ClassA中,bytes的初始值为null,但在ClassB中,从流程的开始存储了两个值,这两个值都发生了是0。你可以想象这可能会与其他参考型和价值型字段一起发挥作用。

当GC运行时,另一个区别就出现了。如果我没有弄错(尽管我不相信这是规范的行为),静态成员在进程终止之前不会被垃圾收集。除此之外,你可以(松散地)将它们视为一个简单的,编译器强制的类的单例实例。


确定值的“延迟加载”的最简单方法是在静态构造函数中放置一个副作用调用。像,

public class Foo
{
    static Foo()
    {
        Console.WriteLine("In static constructor!");
    }

    public static void Bar()
    {
        Console.WriteLine("In Bar!");
    }
}

在一个小型控制台应用程序的上下文中,您可以轻松确定何时调用构造函数,并推断这也是静态成员被放入内存的时候。

void Main(string[] args)
{
    Console.WriteLine("First line!");
    Foo.Bar();
    Console.WriteLine("Last line!");
}

此计划outputs

First line!
In static constructor!
In Bar!
Last line!

您也可以在C#5.0规范(§10.12静态构造函数)中找到此信息,

  

封闭类类型的静态构造函数在给定的应用程序域中最多执行一次。静态构造函数的执行由应用程序域中发生的以下第一个事件触发:

     
      
  • 创建了类类型的实例。
  •   
  • 引用类类型的任何静态成员。
  •   

在外行人的术语中,静态成员在您第一次使用静态类时初始化。如果它从未使用过,它们就永远不会被初始化。


为了让我的观点回家,让我们来看看最后一对方法。

static class Foo
{
    public static int X = 2;
    public static byte[] Bytes = new byte[] { 24, 66 };
}
class Bar
{
    public int X = 2;
    public byte[] Bytes = new byte[] { 24, 66 };
}

单步执行(在IL级别),与这些类的交互将完全不同。

考虑一种方法:

void Main(string[] args)
{
    // Foo is put into memory when the process spins up:
    //     X : 0
    //     Bytes : null

    Console.Write(Foo.X); // During this line, Foo is initialized
                          //     X : 2
                          //     Bytes : 24, 66

    Console.Write(Foo.X); // Nothing happens

    Bar b = new Bar(); // During this line, Bar is put into memory, then initialized
                       //     X : 0, then "instantaneously" becomes 2
                       //     Bytes : null, then "instantaneously" becomes 24, 66

    Console.Write(b.X); // Nothing happens

    // Eventually, b will be cleared from memory by the garbage collector
    // but Foo will not be, until the program closes. This sample is much
    // too small to display that difference.
}