Compilation error. Using properties with struct

2019-02-12 08:41发布

问题:

Please explain the following error on struct constructor. If i change struct to class the erros are gone.

public struct DealImportRequest
{
    public DealRequestBase DealReq { get; set; }
    public int ImportRetryCounter { get; set; }

    public DealImportRequest(DealRequestBase drb)
    {
        DealReq = drb;
        ImportRetryCounter = 0;
    }
}
  • error CS0188: The 'this' object cannot be used before all of its fields are assigned to
  • error CS0843: Backing field for automatically implemented property 'DealImportRequest.DealReq' must be fully assigned before control is returned to the caller. Consider calling the default constructor from a constructor initializer.

回答1:

As the error message recommends, you can resolve this by calling the default constructor from a constructor initializer.

public DealImportRequest(DealRequestBase drb) : this()
{
   DealReq = drb;
   ImportRetryCounter = 0;
}

From the language specification:

10.7.3 Automatically implemented properties

When a property is specified as an automatically implemented property, a hidden backing field is automatically available for the property, and the accessors are implemented to read from and write to that backing field. [...] Because the backing field is inaccessible, it can be read and written only through the property accessors, even within the containing type. [...] This restriction also means that definite assignment of struct types with auto-implemented properties can only be achieved using the standard constructor of the struct, since assigning to the property itself requires the struct to be definitely assigned. This means that user-defined constructors must call the default constructor.

The other (more verbose) alternative, of course, is to manually implement the properties and set the backing fields yourself in the constructor.

Do note that the struct you have there is mutable. This is not recommended. I suggest you either make the type a class (your compilation problems should go away immediately) or make the type immutable. The easiest way to accomplish this, assuming the code you have presented is the entire struct, would be to make the setters private (get; private set;). Of course, you should also make sure that you don't add any mutating methods to the struct afterwards that rely on private access to modify the fields. Alternatively, you could back the properties with readonly backing fields and get rid of the setters altogether.



回答2:

The code you have is equivalent to the following code:

public struct DealImportRequest
{
    private DealRequestBase _dr;
    private int _irc;
    public DealRequestBase DealReq
    {
      get { return _dr; }
      set { _dr = value; }
    }
    public int ImportRetryCounter
    {
      get { return _irc; }
      set { _irc = value; }
    }
    /* Note we aren't allowed to do this explicitly - this is didactic code only and isn't allowed for real*/
    public DealImportRequest()
    {
        this._dr = default(DealRequestBase); // i.e. null or default depending on whether this is reference or value type.
        this._irc = default(int); // i.e. 0
    }
    public DealImportRequest(DealRequestBase drb)
    {
        this.DealReq = drb;
        this.ImportRetryCounter = 0;
    }
}

Now, all I have done here is remove the syntactic sugar that:

  1. Implements automatic properties.
  2. Works out which members are dealt with relative to this.
  3. Gives all structs a default no-parameter constructor.

The first two are optional (you could write them explicitly if you wished) but the third is not - we aren't allowed to write our own code for a struct's parameterless constructor, we have to go with one that works like the one in the code above being given to us automatically.

Now, looked at here, suddenly the meaning of the two errors becomes clear - your constructor is implicitly using this before it's fields are assigned (error 188) and those fields are those backing the automatic properties (error 843).

It's a combination of different automatic features that normally we don't have to think about, but in this case don't work well. We can fix this by following the advice in the error message for 843 and calling the default constructor as part of your explicit constructor:

public DealImportRequest(DealRequestBase drb)
    :this()
{
    DealReq = drb;
    ImportRetryCounter = 0;
}

Considering this in relation to my expanded version of your code above, you can see how this solves the problem, because it calls the constructor that assigns to the backing fields before it proceeds.



回答3:

I would recommend not using auto-properties with structures unless you have a good reason to use them. Wrapping a class field in a read-write property is useful because it makes it possible for an instance to control the circumstances where it may be read or written, and take action when a read or write takes place. Further, code within an object instance can identify the instance being acted upon, and may thus perform a special action only when reading and writing a particular instance. Using an auto-property in an early version of a class will make it possible for future versions of the class to use a manually-implemented property including the aforementioned benefits while retaining compatibility with already-compiled client code. Unfortunately, wrapping a struct field in a read-write property doesn't offer those same benefits because the fields of one struct instance can be copied to another without either instance having any say in the matter. If the semantics of a struct allow a property to be written with arbitrary values in most instances [as would be the case for an auto-property], then any legitimate replacement would be semantically equivalent to a field.