简体   繁体   English

为什么FieldInfo.GetValue(null)在静态构造函数中不起作用

[英]Why is FieldInfo.GetValue(null) not working in static constructor

See the code below. 请参见下面的代码。 I want a class that automatically enumerates all the defined static readonly instances of its own type (see TestClass as an example, it defines 3 static readonly instances of its own type). 我想要一个能够自动枚举其自身类型的所有已定义静态只读实例的类(例如,请参见TestClass,它定义了其自身类型的3个静态只读实例)。

I want this automation because I want to loop over the defined types and not risk the change of forgetting to add a new instance to the list of All . 我想要这种自动化,因为我想遍历定义的类型,而不必冒险忘记将新实例添加到All列表中而进行更改。

Ok, I have it working, that is not the point. 好吧,我已经开始工作了,那不是重点。 But why doesn't FillAll work when called from a static constructor? 但是为什么从静态构造函数调用FillAll不起作用? See the commented static constructor in DefinedInstancesBase<T> code. 请参见DefinedInstancesBase<T>代码中DefinedInstancesBase<T>注释的静态构造函数。 I mean FieldInfo.GetValue(null) returns null in the static constructor, though the debugger has already hit creating the static readonly instances before the FieldInfo.GetValue(null) is called. 我的意思是,尽管调试器已经在调用FieldInfo.GetValue(null)之前创建了静态只读实例,但FieldInfo.GetValue(null)在静态构造函数中返回null。

I'm very curious why it doesn't work. 我很好奇为什么它不起作用。 Is this by design? 这是设计使然吗?

public abstract class DefinedInstancesBase<T>
{
    public static IList<T> All
    {
        get
        {
            if (_All == null)
            {
                FillAll();
            }
            return _All;
        }
    }

    //Why this doesn't work? No idea.
    //static DefinedInstancesBase()
    //{
    //    FillAll();
    //}

    private static void FillAll()
    {
        var typeOfT = typeof(T);
        var fields = typeOfT.GetFields(BindingFlags.Public | BindingFlags.Static);
        var fieldsOfTypeT = fields.Where(f => f.FieldType == typeOfT);
        _All = new List<T>();
        foreach (var fieldOfTypeT in fieldsOfTypeT)
        {
            _All.Add((T)fieldOfTypeT.GetValue(null));
        }
    }

    private static List<T> _All = null;
}

[TestClass]
public class DefinedInstancesTest
{
    [TestMethod]
    public void StaticReadOnlyInstancesAreEnumerated()
    {
        //Given
        var expectedClasses = new List<TestClass>
        {
            TestClass.First,
            TestClass.Second,
            TestClass.Third,
        };

        //When
        var actualClasses = TestClass.All;

        //Then
        for (var i=0; i<expectedClasses.Count; i++)
        {
            Assert.AreEqual(expectedClasses[i].Id, actualClasses[i].Id);
        }
    }

    private class TestClass : DefinedInstancesBase<TestClass>
    {
        public static readonly TestClass First = new TestClass(1);
        public static readonly TestClass Second = new TestClass(2);
        public static readonly TestClass Third = new TestClass(3);

        public int Id { get; private set; }

        private TestClass(int pId)
        {
            Id = pId;
        }
    }
}

There are two separate issues at work here. 这里有两个单独的问题在起作用。

  1. There is a typo in your static constructor in the code above. 上面的代码中的static构造函数中有一个错字。 Try changing static DefinedInstances() to static DefinedInstancesBase() , because currently it is just specified as a private static function. 尝试将static DefinedInstances()更改为static DefinedInstancesBase() ,因为当前它只是被指定为私有静态函数。
  2. The second and more important issue is to understand the order that the various constructors are being called in. What is happening is that the static constructor on the base abstract class is getting triggered by the instantiation (during member initializer) of the First field in the derived class. 第二个也是更重要的问题是要了解各种构造函数的调用顺序。发生的情况是,基本抽象类上的静态构造函数是由(在成员初始化程序中) First字段的实例化触发的。派生类。 Therefore, First is still null when the static constructor of DefinedInstancesBase class is being called (and thus the FindAll() method). 因此,在调用DefinedInstancesBase类的static构造函数(并因此调用FindAll()方法)时, First仍然为null。

See the following code (slightly modified to better illustrate the issue) and output: 请参见以下代码(稍作修改以更好地说明问题)并输出:

public void Main()
{
    DefinedInstancesTest dit = new DefinedInstancesTest();
    dit.StaticReadOnlyInstancesAreEnumerated();
}

public abstract class DefinedInstancesBase<T>
{
    public static IList<T> All
    {
        get
        {
            //if (_All == null)
            //    FillAll();
            return _All;
        }
    }

    // correctly named static ctor
    static DefinedInstancesBase() { FillAll(); }

    private static void FillAll()
    {
        Console.WriteLine("FillAll() called...");
        var typeOfT = typeof(T);
        var fields = typeOfT.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
        var fieldsOfTypeT = fields.Where(f => f.FieldType == typeOfT);
        _All = new List<T>();
        foreach (var fieldOfTypeT in fieldsOfTypeT)
        {
            _All.Add((T)fieldOfTypeT.GetValue(null));
        }
    }

    private static List<T> _All = null;
}

//[TestClass]
public class DefinedInstancesTest
{
    //[TestMethod]
    public void StaticReadOnlyInstancesAreEnumerated()
    {
        //Given
        var expectedClasses = new List<TestClass>
        {
            TestClass.First,
            TestClass.Second,
            TestClass.Third,
        };

        //When
        var actualClasses = TestClass.All;

        //Then
        for (var i=0; i<expectedClasses.Count; i++)
        {
            //Assert.AreEqual(expectedClasses[i].Id, actualClasses[i].Id);
            if (expectedClasses[i].Id != actualClasses[i].Id)
              Console.WriteLine("not equal!");
        }
    }

    private class TestClass : DefinedInstancesBase<TestClass>
    {
        public static readonly TestClass First;
        public static readonly TestClass Second;
        public static readonly TestClass Third;

        public int Id { get; private set; }

      static TestClass()
      {
        Console.WriteLine("TestClass() static ctor called...");
        First = new TestClass(1);
        Second = new TestClass(2);
        Third = new TestClass(3);
      }

        private TestClass(int pId)
        {
          Console.WriteLine("TestClass({0}) instance ctor called...", pId);
          Id = pId;
        }
    }
}

TestClass() static ctor called...
// the line "First = new TestClass(1);" now triggers the base class static ctor to be called,
// but the fields First, Second, and Third are all still equal to null at this point!
FillAll() called...
TestClass(1) instance ctor called...
TestClass(2) instance ctor called...
TestClass(3) instance ctor called...
// this null reference exception to be expected because the field value actually was null when FindAll() added it to the list
Unhandled Expecption: 
System.NullReferenceException: Object reference not set to an instance of an object.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM