Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused with little used Value Type initialization

Tags:

c#

value-type

The following code is illegal:

public struct MyStruct
{
    public MyStruct(int a, int b)
    {
        this.a = a;
        this.b = b;
    }

    public int a;
    public int b;
}

//now I want to cache for whatever reason the default value of MyStruct
MyStruct defaultValue;
...
if (foo != defaultValue) //use of unassigned variable...

The way things should be done is obviously:

MyStruct defaultValue = default(MyStruct) //or new MyStruct();
...
if (foo != defaultValue) //compiler is happy

But the following is also allowed (I didn't know this and stumbled upon it by accident):

MyStruct defaultValue;
defaultValue.a = 0;
defaultValue.b = 0;
...
if (foo != defaultValue) //legal

I guess the compiler verifies that all fields of the struct have been initialized and therefore allows this code to compile. Still I find it confusing with how the rest of the language works. After all, you are basically using an unassigned variable in the C# way of seeing things.

Things get even more confusing if you consider the following code:

public struct MyStruct
{
    public MyStruct(int a, int b)
    {
        this.a = a;
        this.b = b;
        this.c = (a + b).ToString();
    }

    public int a;
    public int b;
    internal string c;
}

The following code is illegal becuase in this case we haven't assigned all visible fields:

MyStruct defaultValue;
defaultValue.a = 0;
defaultValue.b = 0;
...
if (foo != defaultValue) //use of unassigned variable...

Visible is the key word here, because if MyStruct were to be defined in a referenced assembly then c is not visible and the compiler will not complain and the previous code would be perfectly valid. Confusing again.

Can somebdoy please explain why its allowed to initialize a struct in C# in such manner? Why not disallow it completely so there is a more unified experience when dealing with any type of value in the language?

EDIT 1: I made a mistake in the last example. The compiler will be happy only if the not visible field is of reference type. Even more confusing. Is this last case a known bug in the compiler or is there a sane reason for it to work the way it does?

Changed last example to a valid case.

EDIT 2: I'm still a litte befuddled with how value-type initialization works. Why isn't the following allowed for instance:

struct MyStruct
{ 
    public int A { get; set; } //Auto-implemented property
    public int B { get; set; } //Auto-implemented property
}

MyStruct defaultValue;
defaultValue.A = 0;  //use of unassigned variable...
defaultValue.B = 0;

The way I see it, there is little doubt that all fields MyStruct are initialized. I can see the reasoning of why this wouldn't be allowed if properties were not auto-implemented as it is arguably possible that the setters do not garantee that all fields are set. But in auto-implemented properties the compiler knows with 100% certainty that the fields will be set if the properties are (after all its code that the compiler generates for you).

Finally, a small theoretical case with evidently no practical use:

struct MyUselessStruct
{
}

MyUselessStruct defaultValue;
Console.WriteLine(defaultValue.ToString()); //hehe did I get away with using an unassigned variable?

Then why isn't this allowed:

Object object;
if (object == null) .... //use of unassigned variable

I find both cases similar in concept and I'd expect them both to work the same way in C#. I still don't understand why this seemingly useless differentiation in the way value-type variables can be initialized and what is it's practical use (on top of the inconsistencies I explained in the first part of my question)

like image 786
InBetween Avatar asked Jun 22 '11 09:06

InBetween


1 Answers

The spec explicitly allows this; 12.3 in ECMA334v4

  • A struct-type variable is considered definitely assigned if each of its instance variables is considered definitely assigned.

However, mutable structs are evil. So I strongly suggest you DO NOT do this.

Make the fields private readonly, set them via a custom constructor, and access them via a get-only property:

public struct MyStruct
{
    public MyStruct(int a, int b)
    {
        this.a = a;
        this.b = b;
    }
    public int A { get { return a; } }
    public int B { get { return b; } }
    private readonly int a, b;
    internal int C { get { return a + b; } }
}
like image 193
Marc Gravell Avatar answered Oct 15 '22 00:10

Marc Gravell