Статические объекты являются нулевыми/не инициализированы в нужное время

Я использую описанный здесь типобезопасный шаблон перечисления. Мне нужно вложить одно типовое перечисление в другое. Дочернее свойство (статический объект) имеет значение NULL во время создания родительского конструктора. Кажется, что дочерний конструктор не вызывается, и я получаю некоторые ошибки (родительский и дочерний элементы могут сбивать с толку, но это объясняет иерархию)

Вот пример (я использую netMF):

public class MyDeviceSetting //parent
{
        public readonly string Name;
        public MyUnit SettingUnit;
        public readonly MyUnit.UnitPurpose UnitPurpose;

          #region MY STATIC SETTINGS
        //UNIT SETTINGS
        public static MyDeviceSetting TempUnits = new MyDeviceSetting("TempUnits", MyUnit.mm); //MyUnit.mm is null. Why?
        public static MyDeviceSetting BLAH = new MyDeviceSetting("BLAH", MyUnit.inch);//MyUnit.inch is null. Why?
          #endregion



        /// <summary>
        /// This is the MAIN PRIVATE Constructor
        /// </summary>
        /// <param name="?"></param>
        private MyDeviceSetting(string name, MyUnit defaultUnit)
        {
            Name = name;
            SettingUnit = defaultUnit;//NULL
            UnitPurpose = SettingUnit.Purpose; //fails because SettingUnit is NULL


        }


    }


public sealed class MyUnit
{
    private static int Count = 0;

    //these are used to store and identify the unit in memory
    public readonly int UnitID;
    public readonly int TestID;

    public enum UnitPurpose
    {
        DISTANCE,
        SPEED,
        TEMPERATURE,
        TIME,
        CLOCK,
        NO_UNITS
    }

    public readonly string DisplayName;
    public readonly string Abbreviation;
    public readonly string Name;
    public readonly UnitPurpose Purpose;

    #region My Units
    public static readonly MyUnit mm = new MyUnit("Milimeters", "mm", "mm", UnitPurpose.DISTANCE, 1);
    public static readonly MyUnit inch = new MyUnit("inch", "inch", "in", UnitPurpose.DISTANCE, 2);



    #endregion

    private MyUnit(string name,
                   string displayName,
                   string abbreviation,
                   UnitPurpose unitPurpose,
                   int unitID)
    {
        Name = name;
        DisplayName = displayName;
        Abbreviation = abbreviation;
        Purpose = unitPurpose;
        UnitID = unitID;
        TestID = Count;
        Count++;

    }


}

Как убедиться, что child НЕ равно нулю? Есть ли обходной путь? Изменить: This Post гарантирует, что это должно просто работать, но в моем случае это не работает. Нулевой


person GisMofx    schedule 12.08.2015    source источник
comment
Предоставленный вами код не компилируется, но после внесения тривиальных изменений, необходимых для его компиляции, он работает нормально. Пожалуйста, предоставьте короткий, но полный пример, который на самом деле демонстрирует проблему.   -  person Jon Skeet    schedule 12.08.2015
comment
@JonSkeet Я попытаюсь извлечь пример из реального кода - я исправил текущий код   -  person GisMofx    schedule 12.08.2015
comment
Я бы попробовал использовать статический конструктор в каждом классе и инициализировать там каждое статическое значение перечисления.   -  person Philippe Paré    schedule 12.08.2015
comment
Почему ParentEnum и ChildEnum имеют частные конструкторы? нет способа создать их экземпляр.   -  person JuanK    schedule 12.08.2015
comment
@JuanK Это цель Enum   -  person Eser    schedule 12.08.2015
comment
@JuanK проверьте ссылку в первом предложении моего вопроса. Нет необходимости создавать экземпляр.   -  person GisMofx    schedule 12.08.2015
comment
Мне кажется вероятным, что между двумя классами в вашем реальном коде существует цикл зависимости инициализации.   -  person zneak    schedule 12.08.2015
comment
Я здесь с Джоном Скитом. Я запускаю ваш код, и он работает. Поле Child в ParentEnum не является null.   -  person juharr    schedule 12.08.2015
comment
@zneak только у Родителя есть ссылка на ребенка ... Я искал это. Если я не пропущу концепцию здесь   -  person GisMofx    schedule 12.08.2015
comment
@juharr Да, этот пример работает, но мой реальный код не работает. Я пытаюсь обновить свой пример, чтобы продемонстрировать эту проблему.   -  person GisMofx    schedule 12.08.2015
comment
@JonSkeet Я обновил код примера, и он демонстрирует эту проблему.   -  person GisMofx    schedule 12.08.2015
comment
@zneak Пример кода обновления демонстрирует проблему, которую я описываю.   -  person GisMofx    schedule 12.08.2015
comment
Возможно, вы также захотите обновить периферийные устройства вокруг кода; там нет child, поэтому я не могу понять, в чем проблема.   -  person Nyerguds    schedule 12.08.2015
comment
@GusMofx Я проверил ваш последний обновленный код, но все выглядит нормально, код, который вы говорите нам, должен дать сбой или привести к нулю ... работает нормально. :С   -  person JuanK    schedule 12.08.2015
comment
В вашем примере нет метода Main, который мы могли бы запустить, и вам действительно нужно сократить его до только того, что вам нужно для демонстрации проблемы.   -  person Jon Skeet    schedule 12.08.2015
comment
Итак, вы, кажется, думаете, что проблема в том, что SettingUnit равно нулю. Вы также, кажется, говорите, что defaultUnit равно нулю, и в своем коде вы устанавливаете SettingUnit = defalutUnit. Таким образом, SettingUnit равно нулю, потому что вы установили его равным нулю. Итак, либо вы спрашиваете, почему переменная, которую я установил в значение null, теперь равна нулю, что является нонсенсом, либо в вашем вопросе отсутствует что-то еще.   -  person shf301    schedule 12.08.2015
comment
@ shf301, defaultUnit передается в конструктор. defaultUnit — это статический объект из дочернего безопасного перечисления.   -  person GisMofx    schedule 12.08.2015
comment
@GisMofx Кто-то передает null в конструктор. Я не знаю почему, потому что вы не показываете нам этот код.   -  person shf301    schedule 12.08.2015
comment
@ shf301 Конструктор в MyUnit, похоже, не вызывается перед конструктором в MyDeviceSetting Весь код есть.   -  person GisMofx    schedule 12.08.2015
comment
Используйте стек, чтобы увидеть, какой вызов конструктора передает null, и покажите эту строку кода.   -  person juharr    schedule 12.08.2015
comment
@juharr В моем примере две строки под //UNIT SETTINGS . MyUnit.mm и MyUnit.inch равны NULL.   -  person GisMofx    schedule 12.08.2015
comment
Ваш последний пример отлично работает для меня. Либо отсутствует какая-то важная часть, либо в вашей настройке происходит что-то странное. Вы пытались выполнить Rebuild перед отладкой?   -  person juharr    schedule 12.08.2015
comment
@juharr Я развертываю приложение netMF, а не консольное приложение. Когда я выполняю код, MyDeviceSetting элементы инициализируются перед MyUnit элементами.   -  person GisMofx    schedule 12.08.2015
comment
В этом случае это может быть ошибка в netMF. Вы должны указать это в вопросе и добавить для него тег.   -  person juharr    schedule 12.08.2015
comment
Я выясняю, не является ли это проблемой netMF. В противном случае, возможно ли обходное решение?   -  person GisMofx    schedule 12.08.2015


Ответы (4)


Похоже, это ошибка/ограничение платформы .Net Micro. Он не полностью поддерживает статические конструкторы. Вот кто-то, кто сообщает о той же проблеме: https://msdn.microsoft.com/en-us/library/Cc533032.aspx

документация для NetCF 3.0 содержит следующее предупреждение:

Не используйте статические конструкторы. Они еще не полностью поддерживаются .NET Micro Framework.

Из этого сообщения в блоге также следует скажем, что (по крайней мере, в версии 2.0) вызовы статических конструкторов были сериализованы:

Есть некоторые вещи, которые нельзя сделать в .NET Compact Framework в статическом конструкторе, но они возможны в полной версии .NET Framework. По сути, все статические конструкторы выполняются сериализованным способом в .NET Compact Framework V2.

В этом посте это обсуждается в контексте взаимоблокировок, но я считаю, что это причина того, что это не работает.

К сожалению, это означает, что вы не можете полагаться на статику, и вам придется самостоятельно обрабатывать инициализацию и блокировку. Что-то вроде этого должно работать:

private static MyUnit inch;

public static MyUnit Inch
{
    get
    {
        if (inch == null)
            inch = new MyUnit("inch", "inch", "in", UnitPurpose.DISTANCE, 2);
         return inch;
    }
}

Это, к сожалению, теряет потокобезопасность, которую дал вам статический конструктор. Это будет трудно исправить, так как вы не можете полагаться на статические члены, поскольку, как мы видели, вы не можете полагаться на их инициализацию.

person shf301    schedule 12.08.2015
comment
вызовы статических конструкторов были сериализованы: Интересно. Меня вдохновило переименовать MyUnit в MyAUnit, и кажется, что инициализация упорядочена в алфавитном/буквенно-цифровом/любом порядке. Теперь он вызывает эти конструкторы раньше, и я больше не получаю ошибок времени выполнения NULL! - person GisMofx; 12.08.2015

В качестве обходного пути вы можете переместить класс MyUnit в другой исходный файл с именем MyUnit.cs, это приведет к тому, что .netmf CLR сначала загрузит этот класс.

Я уже тестировал в своей среде запуск .Netmf 4.3 RTM. введите здесь описание изображения

person JuanK    schedule 12.08.2015

В дополнение к @shf301 с объяснением, почему он работает неправильно, кажется, что конструкторы вызываются в каком-то алфавитном/буквенно-цифровом порядке при компиляции.

Переименование дочернего класса в MyAUnit делает его предшествующим MyDeviceSetting и, по-видимому, запускает MyAUnit статических членов перед MyDeviceSettingстатическими членами.

Кажется, это работает, но пока что это что-то вроде «взлома».

person GisMofx    schedule 12.08.2015

Похоже, вашу проблему очень сложно воспроизвести, в любом случае нужно быть прагматичным.

Вы можете сделать так, чтобы MyUnit статическое поле было инициализировано до MyDeviceSetting статических полей, просто сначала вызвав MyUnit.

И это довольно просто, просто создайте фиктивную переменную перед любой другой вещью в вашей программе.

public class MyDeviceSetting //parent
{
    static MyDeviceSetting()
    {
        var dummy = MyUnit.inch;
    }
}

Таким образом, фиктивная переменная никогда не используется, но инициализируется статический экземпляр MyUnit.

После этого любая активность с MyDeviceSetting будет инициализирована MyUnit.

person JuanK    schedule 12.08.2015
comment
Я старался. Он по-прежнему инициализируется после класса Parent. - person GisMofx; 12.08.2015
comment
Этого не должно быть. Где вы создаете фиктивный var? Просто сделайте это в первой строке кода, выполняемой после запуска вашей программы. - person JuanK; 12.08.2015
comment
@GisMofx Я изменил код, поместив фиктивную переменную в новый статический конструктор MyDeviceSetting. Статический конструктор + фиктивная инициализация должны работать. - person JuanK; 12.08.2015
comment
Вы можете попробовать с netMF? - person GisMofx; 12.08.2015
comment
Давайте продолжим это обсуждение в чате. - person JuanK; 13.08.2015