4

I am using the type-safe enum pattern outlined here. I have a need to nest one type-safe enum into another. The child property(static object) is NULL at the time the parent constructor is created. It seems like the child constructor is not called and I'm getting some errors.(parent and child my be confusing, but it explains the hierarchy)

Here's an example(I'm using 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++;

    }


}

How do I ensure that child is NOT null? Is there a work-around? Edit: This Post ensures that this should just work, but in my case it's not working. Null

Community
  • 1
  • 1
GisMofx
  • 982
  • 9
  • 27
  • 5
    The code you've provided doesn't compile, but after making the trivial changes required to make it compile, it works fine. Please provide a short but complete example which actually demonstrates the problem. – Jon Skeet Aug 12 '15 at 17:36
  • @JonSkeet I'll try to extract an example from the real code - I fixed the current code – GisMofx Aug 12 '15 at 17:37
  • I would try using a static constructor in each class and initialize each static enum values in there – Philippe Paré Aug 12 '15 at 17:38
  • Why ParentEnum and ChildEnum have private constructors? there's no way to instantiate them. – JuanK Aug 12 '15 at 17:46
  • 1
    @JuanK It is the purpose of being an *Enum* – Eser Aug 12 '15 at 17:47
  • @JuanK check the link in the first sentence of my question. No need to instantiate it. – GisMofx Aug 12 '15 at 17:47
  • It looks probable to me that there's an initialization dependency cycle between the two classes in your actual code. – zneak Aug 12 '15 at 17:48
  • I'm with Jon Skeet here. I run your code and it works. The `Child` field of the `ParentEnum` are not `null`. – juharr Aug 12 '15 at 17:53
  • @zneak only the Parent has a reference to the child...I was looking for that. Unless I'm missing a concept here – GisMofx Aug 12 '15 at 17:55
  • @juharr Yes, this example seems to work, but my real code does not. I am trying to update my example to exhibit this issue. – GisMofx Aug 12 '15 at 17:55
  • @JonSkeet I updated the example code and and it exhibits this issue. – GisMofx Aug 12 '15 at 18:10
  • @zneak The update example code exhibits the issue I describe – GisMofx Aug 12 '15 at 18:11
  • Might want to update the peripherals around the code too; there's no `child` in there, so I can't begin to see what the actual problem is. – Nyerguds Aug 12 '15 at 18:16
  • 1
    @GusMofx I have tested you're last updated code, but everything looks ok, the code you tell us must fail or resulting in null... is working fine. :S – JuanK Aug 12 '15 at 18:19
  • Your example doesn't have a `Main` method for us to run - and you really need to cut it down to *just* what you need to demonstrate the problem. – Jon Skeet Aug 12 '15 at 18:39
  • So you appear to think the issue is that `SettingUnit` is null. You also seem to be saying that `defaultUnit` is null and in your code you set `SettingUnit = defalutUnit`. So `SettingUnit` is null because you set it to null. So either you question is why is a variable I set to null now null, which is nonsense, or there is something else missing from your question. – shf301 Aug 12 '15 at 18:52
  • @shf301, `defaultUnit` is passed into the constructor. The defaultUnit is a static object from the child type-safe enum. – GisMofx Aug 12 '15 at 18:55
  • 1
    @GisMofx Someone's passing null into the constructor. I don't know why because you are not showing us that code. – shf301 Aug 12 '15 at 18:59
  • @shf301 The constructor in `MyUnit` doesn't seem to get called before the constructor in `MyDeviceSetting` All the code is there. – GisMofx Aug 12 '15 at 19:01
  • Use the stack to see which call to the constructor is passing `null` and show that line of code. – juharr Aug 12 '15 at 19:08
  • @juharr In my example the two lines beneath `//UNIT SETTINGS` . `MyUnit.mm` and `MyUnit.inch` are NULL. – GisMofx Aug 12 '15 at 19:13
  • Your latest example works fine for me. Either there is some important piece missing or there is something weird going on in your setup. Have you tried doing a Rebuild before debugging? – juharr Aug 12 '15 at 19:17
  • @juharr I'm deploying a netMF application, not a console app. When I step through the code, `MyDeviceSetting` members get initialized before `MyUnit` members. – GisMofx Aug 12 '15 at 19:23
  • 1
    In that case this sounds like it might be a bug with netMF. You should mention that in the question and add a tag for it. – juharr Aug 12 '15 at 19:27
  • I'm investigating if it's a netMF issue. Otherwise, is there a possible work-around? – GisMofx Aug 12 '15 at 20:06

4 Answers4

1

This appears to be a bug/limitation of the .Net Micro framework. It doesn't fully support static constructors. Here's someone who is reporting the same issue: https://msdn.microsoft.com/en-us/library/Cc533032.aspx

The documentation for NetCF 3.0 contains the following warning:

Do not use static constructors. They are not yet fully supported by the .NET Micro Framework.

From this blog post also seems to say that (at least as of 2.0) calls to static constructors were serialized:

There are some things that cannot be done in .NET Compact Framework in a static constructor which are possible in the full .NET Framework. Basically, all static constructors when are executed in a serialized fashion in .NET Compact Framework V2

That post discusses it in the context of deadlocks, but I believe it is the reason this isn't working.

Unfortunately for that means you can't rely on the statics and will have to handle initialization and locking yourself. Something like this should work:

private static MyUnit inch;

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

This unfortunately looses the thread safety that a static constructor gave you. That will be hard to fix since you can't rely on a static members since as we've seen you can't rely on them being initialized.

shf301
  • 31,086
  • 2
  • 52
  • 86
  • **_calls to static constructors were serialized:_** Interesting. I was inspired to rename `MyUnit` to `MyAUnit` and it seems the initialization is ordered alphabetically/alphanumerically/whatever. Now it calls those constructors before and I no longer get NULL runtime errors! – GisMofx Aug 12 '15 at 20:37
1

As a workaround you can move MyUnit class to another source file called MyUnit.cs, this will cause .netmf CLR load this class at first.

I have already tested in my environment running .Netmf 4.3 RTM. enter image description here

JuanK
  • 2,056
  • 19
  • 32
0

Looks like your problem is very difficult to replicate, any way one needs to be pragmatic.

You can made MyUnit static field to be initialized prior to MyDeviceSetting static fields just calling MyUnit at first.

And that's pretty easy ,just create a dummy variable before any other thing on your program.

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

So, dummy var is never used but the static instance of MyUnit is initialized.

After that any activity with MyDeviceSetting will get MyUnit initialized.

JuanK
  • 2,056
  • 19
  • 32
  • I tried. It still initializes after the Parent class. – GisMofx Aug 12 '15 at 20:25
  • This shouldn't be happen. Where are you creating the dummy var? Just do it in the first line of code executed once your program is launched. – JuanK Aug 12 '15 at 20:30
  • @GisMofx I have modified the code putting the dummy var in MyDeviceSetting new static constructor. Static constructor + dummy initialization should work. – JuanK Aug 12 '15 at 20:42
  • Are you able to try with netMF? – GisMofx Aug 12 '15 at 20:43
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/86836/discussion-between-juank-and-gismofx). – JuanK Aug 12 '15 at 21:26
0

In addition to @shf301 with the reasoning why it's not working correctly, It seems that the constructors are called in some type of alphabetical/alphanumerical order when compiled.

Renaming the Child Class to MyAUnitmakes it come before MyDeviceSetting and seems to trigger MyAUnit static members before MyDeviceSettingstatic memebers.

This seems to work, yet somewhat of a "hack" for the time being.

GisMofx
  • 982
  • 9
  • 27