【问题标题】:Why is FieldInfo.GetValue(null) not working in static constructor为什么 FieldInfo.GetValue(null) 在静态构造函数中不起作用
【发布时间】:2015-06-24 12:05:32
【问题描述】:

请参阅下面的代码。我想要一个自动枚举其自身类型的所有已定义静态只读实例的类(以 TestClass 为例,它定义了其自身类型的 3 个静态只读实例)。

我想要这种自动化,因为我想遍历定义的类型,并且不会冒忘记将新实例添加到All 列表中的风险。

好的,我已经成功了,这不是重点。但是为什么FillAll 从静态构造函数调用时不起作用?请参阅 DefinedInstancesBase<T> 代码中注释的静态构造函数。我的意思是 FieldInfo.GetValue(null) 在静态构造函数中返回 null,尽管在调用 FieldInfo.GetValue(null) 之前调试器已经创建了静态只读实例。

我很好奇为什么它不起作用。这是设计使然吗?

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

【问题讨论】:

    标签: c# reflection static-constructor


    【解决方案1】:

    这里有两个不同的问题。

    1. 上面代码中的static 构造函数中有错字。尝试将static DefinedInstances() 更改为static DefinedInstancesBase(),因为目前它只是指定为私有静态函数。
    2. 第二个也是更重要的问题是了解调用各种构造函数的顺序。发生的情况是基础抽象类上的静态构造函数被 @ 的实例化(在成员初始化程序期间)触发987654324@ 派生类中的字段。因此,当 DefinedInstancesBase 类的 static 构造函数被调用时,First 仍然为 null(因此也就是 FindAll() 方法)。

    查看以下代码(稍作修改以更好地说明问题)和输出:

    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.
    

    【讨论】:

    • 我不关注..它编译。 typeof(T) 正在工作。什么意思,能详细点吗?
    • 构造函数必须与其类同名,而你的则不然。您的类称为 DefinedInstancesBase,但(注释掉的)构造函数具有不同的名称 - 它称为 DefinedInstances,即它实际上不是构造函数。
    • 哎呀,我的错。那是我稍后在静态构造函数已经成为注释时应用的重命名。所以这不是问题..
    • 所以本质上,尝试通过在static constructor 中调用FillAll() 来使TestMethod 工作,而不是延迟加载属性All
    • @MikedeKlerk 这本身不是延迟加载问题。这是在 First 字段可以初始化为对象之前调用基类中的静态构造函数。因此,该字段的当前值(在调用 FindAll() 时)正确地返回为 null
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-25
    • 1970-01-01
    • 1970-01-01
    • 2011-05-29
    • 1970-01-01
    • 2011-08-13
    相关资源
    最近更新 更多