21

I have two similar structs in C#, each one holds an integer, but the latter has get/set accessors implemented.

Why do I have to initialize the Y struct with new operator prior to assigning the a field? Is y still a value type when I init it with new?

public struct X
{
    public int a;
}

public struct Y
{
    public int a { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        X x;
        x.a = 1;

        Y y;
        y.a = 2; // << compile error "unused local variable" here

        Y y2 = new Y();
        y2.a = 3;
    }
}
Zong
  • 6,160
  • 5
  • 32
  • 46
pospec4444
  • 364
  • 1
  • 8
  • 5
    I'm kind of surprised `X x; x.a = 1;` works... – Zong Nov 26 '13 at 19:46
  • 3
    [Mutable `struct`s are evil](http://stackoverflow.com/questions/441309/why-are-mutable-structs-evil). I'm very familiar with C#, but I fully expected your example with `X` to not compile. It does, oddly enough. However, `X x; Console.WriteLine(x);` does *not*, and `X x; x.a = 1; Console.WriteLine(x);` *does* work. – Tim S. Nov 26 '13 at 19:47
  • Also, you shouldn't have non-private fields, you should make them properties (like in `Y`). – Tim S. Nov 26 '13 at 19:48
  • 5
    I think we need Jon Skeet or Eric Lippert to explain this magic. Looks like assigning all fields counts as initialization. But in second case it's method calling on uninitialized struct. – Oleh Nechytailo Nov 26 '13 at 19:50
  • @OlehNechytailo Good catch! If there's also a `public int b;` field in `X`, my last example doesn't compile, ("Use of unassigned local variable 'x'") probably because not all fields have necessarily been assigned. – Tim S. Nov 26 '13 at 19:51
  • http://stackoverflow.com/questions/9207488/what-does-the-keyword-new-does-to-a-struct-in-c – TGH Nov 26 '13 at 19:57
  • possible duplicate of [Compiler gives error when struct is not initialized and if we try to access the property but not with variable](http://stackoverflow.com/questions/7529802/compiler-gives-error-when-struct-is-not-initialized-and-if-we-try-to-access-the) – Sriram Sakthivel Nov 26 '13 at 20:06
  • 2
    Here comes the specification! "12.3 Definite assignment" (page 122 of ecma-334). "A struct-type variable is considered definitely assigned if each of its instance variables is considered definitely assigned" – Oleh Nechytailo Nov 26 '13 at 20:13

4 Answers4

14

The reason one is valid while the other is not is that you cannot call methods on uninitialised objects. Property setters are methods too.

public struct X
{
    public int a;
    public void setA(int value)
    { this.a = value; }
}

public struct Y
{
    public int a { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        X x;
        x.setA(1); // A: error
        x.a = 2; // B: okay

        Y y;
        y.a = 3; // C: equivalent to A
    }
}

The reason that is not allowed is that the property setter could observe the uninitialised state of the object. The caller does not know whether the property setter merely sets a field, or does more than that.

  • That's very strange. Would A be allowed *after* B? If so, what if X has another member variable? And if not, then what is the point in allowing members of an uninitialized `struct` to be set at all? – BlueRaja - Danny Pflughoeft Nov 26 '13 at 21:26
  • @BlueRaja-DannyPflughoeft Yes, A after B would be allowed, and no, not if X has another field. When all fields of a struct have been assigned to, the struct as a whole is considered initialised. –  Nov 26 '13 at 21:42
  • Really!? What if the `struct` member is set inside a method, or in a lambda? Does the compiler *seriously* track down which methods alter which struct-members? If not, what if the struct is a member-variable which is initialized in a constructor? This seems to have too many special cases to fit in with the C# design philosophy of *"Keep is simple, stupid"* – BlueRaja - Danny Pflughoeft Nov 26 '13 at 22:55
  • @BlueRaja-DannyPflughoeft If a struct is not fully initialised, you cannot pass it to another method (except with `out`), so no, the compiler does not need to track what other methods do. And class fields, even if they are of a struct type are always initialised, even if the class's constructor doesn't set them. –  Nov 27 '13 at 07:05
7

In first case you just assigning field. It doesn't involve actual using of structure, just setting value into memory (struct address + field offset on stack).

In second case you calling method set_a(int value), but fail because variable is uninitialized.

In third case constructor initializes it for you, so using variable is ok.

Update: Here comes the specification!

"12.3 Definite assignment" (page 122 of ecma-334).

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

Oleh Nechytailo
  • 2,155
  • 17
  • 26
5

This is covered in the C# specification section 5.3 dealing with "Definite Assignment":

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

and:

An initially unassigned variable (Section 5.3.2) is considered definitely assigned at a given location if all possible execution paths leading to that location contain at least one of the following:
* A simple assignment (Section 7.13.1) in which the variable is the left operand.
* ...

As such, this also works:

void Main()
{
    X x;
    x.a = 1;
    x.b = 2;

    x.Dump();
}

public struct X
{
    public int a;
    public int b;
}

You can test this in LINQPad.

Note that there is no way for the compiler to prove that the struct-type variable is considered definitely assigned if you call code on it, and that's what you're doing with a property. As such, before you can use a property on the struct-type variable, it has to be definitely assigned.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
-1

The new operator for value types runs the specified constructor. Unlike with reference types, this is optional, so if you don't use new, the default constructor is implicitly run (you cannot specify your own default constructor, so it always has the effect of giving the default value to the fields for their types).

As for why the compiler error, I'm not really sure. Interestingly, in the C# Interactive window,

public struct Y
{
    public int a { get; set; }
}
Y test;
test.a = 5;

works just fine.

MgSam
  • 12,139
  • 19
  • 64
  • 95