Automatic Properties and Structures Don't Mix?

2019-02-01 03:54发布

问题:

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...

回答1:

From C# 6 onward: this is no longer a problem


Becore C# 6, 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.

With recent versions of C#, you can improve on this:

public int Size { get; }

This can now only be assigned in the constructor.



回答2:

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; }
}


回答3:

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.