1

以下のコードを参照してください。独自の型の定義済みのすべての静的読み取り専用インスタンスを自動的に列挙するクラスが必要です (例として 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;
        }
    }
}
4

1 に答える 1

2

ここでは、2 つの別個の問題が働いています。

  1. static上記のコードのコンストラクターにタイプミスがあります。現在はプライベート静的関数として指定されているだけなので、に変更static DefinedInstances()してみてください。static DefinedInstancesBase()
  2. 2番目のより重要な問題は、さまざまなコンストラクターが呼び出される順序を理解することです。何が起こっているのかというと、基本抽象クラスの静的コンストラクターがFirst、派生フィールドのインスタンス化 (メンバー初期化子中) によってトリガーされることです。クラス。したがって、クラスのコンストラクター(およびメソッド)が呼び出されているFirstときは、まだ nullです。staticDefinedInstancesBaseFindAll()

次のコード (問題をわかりやすくするために少し変更) と出力を参照してください。

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.
于 2015-06-24T12:09:54.403 に答える