我可以在创建实例之前确保静态构造函数完成吗?

时间:2019-06-12 19:33:00

标签: c# .net constructor .net-core static

我的课程有一个静态字段初始化程序,该初始化程序在构造函数中使用:

class Foo {
  private static List<string> list = new List<string>()
  private static object listLock = new Object();

  public Foo(string s) {
    lock(listLock)
      list.Add(s);
  }
}

我的问题是,它偶尔会在静态初始化程序完成之前在构造函数中访问列表,从而在访问NullReferenceException时引起list。根据{{​​3}},只能保证可以在创建实例之前启动静态初始化,而不能保证完成。

是否有某种方法可以确保构造函数仅在静态函数构造函数完成后才被调用(除了像while(list == null){}这样的丑陋的骇客)?

2 个答案:

答案 0 :(得分:3)

Here's a good link on the nuances of before-field-init,但实际上:添加一个显式的静态构造函数应在此处强制运行时。请注意,while (list == null) {} 不起作用,因为会强加运行时的手-基本上,您永远都无法观察到运行时告诉您的谎言

在所示示例中,没关系。您将永远不会看到nulllistLock的{​​{1}}。如果您真的真的要求它们在构造函数之前运行:

list

但是请注意,这并不是真正必要,并且可能会对您的代码产生负面影响,尤其是使用.NET Core 3中的新JIT可以将private static List<string> list; private static object listLock; static Foo() { list = new List<string>(); listLock = new object(); } 字段用额外的伏都语如果它们被完整地初始化了(我知道,如果它们是没有显式静态构造函数的内联字段初始化器,那么它可以做到;我不知道是否可以如果它们是由显式静态构造函数分配的,则执行此操作。

答案 1 :(得分:0)

我无法重现该问题。下面的程序并行创建5个Foo实例,每个Foo实例的构造函数使用类SlowObject的静态实例作为锁,实例化需要500毫秒。尽管如此,SlowObject总是在启动任何Foo构造函数之前创建的。我使用各种版本的.NET Framework,C#版本4、5、6和7对该程序进行了测试,这些程序都具有 Debug Release 配置,并附带和不附带调试器。输出始终是相同的。注释掉静态构造函数没有什么区别。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;

class Program
{
    static Program()
    {
        Program.ConsolePrint("Program Static Constructor");
    }

    static void Main(string[] args)
    {
        ConsolePrint("Starting Tasks");
        var tasks = Enumerable.Range(1, 5).Select(n => Task.Run(() =>
        {
            new Foo(n.ToString());
        })).ToArray();
        ConsolePrint("Waiting Tasks");
        Task.WaitAll(tasks);
        ConsolePrint("Tasks Finished");
        //Console.WriteLine("Foo.list: " + String.Join(", ", Foo.GetItems()));
    }

    public static void ConsolePrint(string line)
    {
        Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " ["
            + Thread.CurrentThread.ManagedThreadId.ToString() + "] > " + line);
    }
}

public class Foo
{
    static Foo()
    {
        Program.ConsolePrint("Foo Static Constructor");
    }

    private static List<string> list = new List<string>();
    private static object listLock = new SlowObject();

    public Foo(string s)
    {
        Program.ConsolePrint("Creating Foo " + s);
        lock (listLock)
        {
            list.Add(s);
        }
    }
}

public class SlowObject
{
    static SlowObject()
    {
        Program.ConsolePrint("SlowObject Static Constructor");
    }

    public SlowObject()
    {
        Program.ConsolePrint("SlowObject Instance Constructor Started");
        Thread.Sleep(500);
        Program.ConsolePrint("SlowObject Instance Constructor Finished");
    }
}

输出:

13:40:24.091 [1] > Program Static Constructor
13:40:24.112 [1] > Starting Tasks
13:40:24.131 [1] > Waiting Tasks
13:40:24.132 [3] > SlowObject Static Constructor
13:40:24.133 [3] > SlowObject Instance Constructor Started
13:40:24.635 [3] > SlowObject Instance Constructor Finished
13:40:24.635 [3] > Foo Static Constructor
13:40:24.637 [3] > Creating Foo 1
13:40:24.637 [4] > Creating Foo 2
13:40:24.643 [3] > Creating Foo 5
13:40:24.639 [5] > Creating Foo 3
13:40:24.641 [6] > Creating Foo 4
13:40:24.647 [1] > Tasks Finished
Press any key to continue . . .