This project is read-only.

Behavior of auto-property initializers

Topics: C# Language Design
Jun 3, 2014 at 2:07 PM
Hi, Roslyn folks!

Can you please clarify the semantics of auto-property initializers in C# 6.0?
For end-user property initializers works as expected most of the time, but when you came to the corner cases like polymorphic properties, things became complicated. Currently, C# initializers behavior looks different from VB.NET one:
public class C
  ' initializer calls virtual property setter here:
  public overridable property Value as Integer = 42
  ' and this one writes directly to backing field:
  public overridable readonly property SomeId as Integer = 43
end class
And C# now always produce direct backing field writes:
class Person(string name, int age) {
  public virtual string Name { get; set; } = name; // backing field assign
  public virtual int Age { get; } = age;           // backing field assign

  // just like backing field assign in field-like event initializers:
  public virtual event EventArgs RoslynReleased = delegate { };
}
Every design choice has it's good and bad parts:
  • Calling virtual property setter may looks like desired behavior, since in C# normally you have direct no access to backing field at all. Every access to polymorphic member should normally be polymorphic too.
  • But we all know that performing virtual calls from constructor is not a good idea at all, since overridden setter may observe some uninitialized state of derived class.
  • On the other hand, making initialization non-polymorphic (like currently implemented) can produce 'lost' state when auto-property will be overridden. So initializer will run, it will initialize backing field of the property, and if both getter and setter are overridden and not accessing base. implementation - backing field state became unreachable. I think this is extremely rare case, but it reminds horror stories about virtual events :)
  • From another point of view, the fact that this code is unequal scares a bit:
class Person {
  public virtual IList<Order> Orders { get; }
  public Person() {
    Orders = new List<Order>();
  }
}

// <=>

class Person {
  public virtual IList<Order> Orders { get; } = new List<Order>();
}
  • One more thing to mention: direct initialization of backing fields solves problems of structs with auto-properties, at least for get-only auto properties. You don't need to add :this() in the example below (to prevent from 'error CS0188: The 'this' object cannot be used before all of its fields are assigned to' compiler error):
struct Vector {
    public int X { get; }
    public int Y { get; }
    
    public Vector(int x, int y) {
        X = x;
        Y = y;
    }
}
But the sad part is you still need to add :this() in the cases of auto-properties with setters, possibly having problems with another initializers (https://roslyn.codeplex.com/workitem/8).

So can you please clarify, what is the current plan?
Jun 3, 2014 at 7:55 PM
This sort of thing is yet another reason I believe auto-properties should have been designed from the get-go to expose the backing field under a known name and/or allow the name and access of the backing field to be specified. If declaration of an auto-property X was defined as creating a backing field f_X [I'd prefer _X, but that wouldn't be CLS complaint if the access were specified as protected], then the meaning of a get-only auto-property would be clear [if the backing field was readonly, it could only be set in the constructor; otherwise the backing field could be set by anything with access to it]. In many cases where a method's purpose is to update multiple properties simultaneously, it is often better to have the method perform all necessary validation at the start, and then write all the fields directly, than to update each field through its associated properties, especially if the validity of one property's new value might depend upon the value of another property which gets written later. Even if properties don't initially perform validation (and can thus be implemented with autoproperties), it would still be helpful to allow code to use both property access when that is appropriate, and field access when that would be appropriate.
Jun 3, 2014 at 11:34 PM
Edited Jun 3, 2014 at 11:37 PM
  • From another point of view, the fact that this code is unequal scares a bit:
class Person {
  public virtual IList<Order> Orders { get; }
  public Person() {
    Orders = new List<Order>();
  }
}

// <=>

class Person {
  public virtual IList<Order> Orders { get; } = new List<Order>();
}
Good thing they're equivalent, then. :) The assignment of a get-only autoprop inside a constructor is the same as an initializer on the property -- it will not call the setter.