views:

1020

answers:

3

Kicking around some small structures while answering this post, I came across the following unexpectedly:

The following structure, using an int field is perfectly legal:

struct MyStruct
{ 
    public MyStruct ( int size ) 
    { 
        this.Size = size; // <-- Legal assignment.
    } 

    public int Size; 
}

However, the following structure, using an automatic property does not compile:

struct MyStruct
{ 
    public MyStruct ( int size ) 
    { 
        this.Size = size; // <-- Compile-Time Error!
    } 

    public int Size{get; set;}
}

The error returned is "The 'this' object cannot be used before all of its fields are assigned to". I know that this is standard procedure for a struct: the backing field for any property must be assigned directly (and not via the property's set accessor) from within the struct's constructor.

A solution is to use an explicit backing field:

struct MyStruct
{ 
    public MyStruct(int size)
    {
        _size = size;
    }

    private int _size;

    public int Size
    {
        get { return _size; }
        set { _size = value; }
    }
}

(Note that VB.NET would not have this issue, because in VB.NET all fields are automatically initialized to 0/null/false when first created.)

This would seem to be an unfortunate limitation when using automatic properties with structs in C#. Thinking conceptually, I was wondering if this wouldn't be a reasonable place for there to be an exception that allows the property set accessor to be called within a struct's constructor, at least for an automatic property?

This is a minor issue, almost an edge-case, but I was wondering what others thought about this...

+11  A: 

You need to call the default constructor for this to work:

public MyStruct(int size) : this()
{
    Size = size;
}

A bigger problem here is that you have a mutable struct. This is never a good idea. I would make it:

public int Size {get; private set;}

Not technically immutable, but close enough.

Marc Gravell
Thanks Marc, this makes perfect sense, thank you, I forgot that we can force the default initialization. And I agree, I would never make a struct mutable, but I was replying to someone else's answer <a href="http://stackoverflow.com/questions/414981/directly-modifying-listt-elements">here</a>.
Mike Rosenblum
I think saying *never* is a bit strong. One should default to immutable, but making it sound like such a mandate "is never a good idea." ;)
Erich Mirabal
Using absolutes is **never** a good idea ;-p (see what I did there...). Indeed, *if you understand the implications*, mutable structs are usable. The problem is that most people don't understand them, and it costs them lots of time and frustration wile they learn.
Marc Gravell
+5  A: 

You can fix this by first calling the default constructor:

    struct MyStruct {
        public MyStruct(int size)
            : this() {
            this.Size = size; // <-- now works
        }

        public int Size { get; set; }
    }
Stormenet
+1  A: 

Another obscure work-around to this problem is one spotted in the temporary Tuple class in the Managed Extensibility Framework (via Krzysztof Koźmic):

public struct TempTuple<TFirst, TSecond>
{
    public TempTuple(TFirst first, TSecond second)
    {
        this = new TempTuple<TFirst, TSecond>(); // Kung fu!
        this.First = first;
        this.Second = second;
    }

    public TFirst First { get; private set; }
    public TSecond Second { get; private set; }

(Full source code from Codeplex: Tuple.cs)

I also note that the documentation for CS0188 has been updated to add:

If you see this error when trying to initialize a property in a struct constructor, the solution is to change the constructor parameter to specify the backing field instead of the property itself. Auto-implemented properties should be avoided in structs because they have no backing field and therefore cannot be initialized in any way from the constructor.

So I take that to mean that the official guidance is to use old-style properties in your structs when you run in to this problem, which is probably less obscure (and more readible) than either of the other two alternatives explored so far.

Daniel Fortunov
Thanks for the update on the error report. Indeed, the most efficient situation for structs is to use an explicit backing field. If one is looking to initialize to something other than 0/null/false, then this requires *two* steps if there is no backing field: null out and then set the actual value.
Mike Rosenblum
The "knung fu" line is really cool, I must say. (Couldn't be done with a class.) But this is the same as having the constructor being declared 'public TempTuple(TFirst first, TSecond second):this()', which, I think, is a cleaner way to do this. (And we still wind up initializing each field *twice*.)
Mike Rosenblum