Core9: readonly auto-properties


[This post is part of a series, “wish-list for future versions of VB“]


 


IDEA: Readonly auto-properties. We should allow read-only properties, which are backed by readonly fields, and can be set by either a property-initializer or through the existing “New … With” syntax.


 


SCENARIO: You want your class to expose a collection, so that users can add or remove elements. But you don’t want them to actually change the list object itself:


    Property p As IList(Of Integer) = New List(Of Integer) From {1, 2, 3}


 


SCENARIO: You want to create a simple immutable record. Currently the language fights against you every step of the way. It could be as easy as this:



Class Point


    ReadOnly Property x As Integer = From New


    ReadOnly Property y As Integer = From New


End Class


 


Dim t As New Point With {.x = 15, .y = 27}


 


The first scenario would be translated into this by the compiler: 


    ReadOnly Property p As IList(Of Integer)


        Get


            Return _p


        End Get


    End Property


 


    Private ReadOnly _p As IList(Of Integer)


 


    Sub New()


        _p = New List(Of Integer) From {1, 2, 3}


    End Sub


The initialization code “_p = …” would be injected into every constructor, as is done with the current auto-properties.


 


As for the second scenario, it is more awkward to implement. We could translate it into the following. It would be an error if the user had already provided a constructor.



Class Point


    ReadOnly Property x As Integer


        Get


            Return _x


        End Get


    End Property


 


    ReadOnly Property y As Integer


        Get


            Return _y


        End Get


    End Property


 


    Private ReadOnly _x As Integer


    Private ReadOnly _y As Integer


 


    Sub New(ByVal x As Integer, ByVal y As Integer)


        _x = x


        _y = y


    End Sub


End Class


 


Dim t As New Point2(x:=15, y:=27) With {}


The “New … With” translation is interesting. What we could say is this: “With {.x=15, .y=27}” will set mutable properties “x” and “y” if they exist (for backwards compatibility). If they don’t exist but there do exist readonly auto-properties of the same name, then they will be moved as named arguments into the constructor.


 


There is a lot of magic going on in translating the second scenario. It might be too much. We would also have to be careful that the user remains able to set attributes conveniently enough, e.g. for serialization.


The first scenario is also not quite answered properly. Usually when you expose an IEnumerable(Of T) you really intend wrap your own private mutable list as a ReadOnlyCollection(Of T), so as to prevent sneaky users from casting the IEnumerable(Of T) into an IList(Of T) and then modifying your list.


Users have also asked for a way to have private setters for auto-properties. It might be that this is a more useful scenario rather than readonly.


 


Provisional evaluation from VB team: This is a decent idea, one that we should consider against the other decent ideas. Readonly datastructures and immutable datastructures are a Good Thing. And we should invent syntax for them that’s as easy or easier than the equivalent syntax for mutable data, so that users can “stumble into a pit of virtue”.


 


 

Comments (5)

  1. Casey Joyce says:

    I’d like having a ReadOnly auto-property that can be set in the class (without creating a corresponding field) but not set outside the class.

    Scenerio: you can set readonly property in constructor (or elsewhere in the class)

    Class Point

    Sub New(x As Integer)

     Me.X = x

    End Sub

    ReadOnly Property X As Integer

    End Class

    Scenerio: but you can’t set outside the class

    Dim point = New Point(1)

    point.X = 2 ‘this won’t compile b/c readonly

  2. Kyralessa says:

    I’d expect readonly properties to work the same way as readonly fields.  Indeed, for collections it makes sense to have them in readonly properties, but it requires too much syntax.

    So the first example (Readonly Property p As IList(Of Integer) etc.) makes sense to me.

    The Point example doesn’t make sense.  I’d expect to have to set a readonly property in the declaration or the constructor just like I do with readonly fields.  So:

    Class Point

    ..ReadOnly Property x As Integer

    ..ReadOnly Property y As Integer

    ..Public Sub New(x As Integer, y As Integer)

    ….Me.x = x

    ….Me.y = y

    ..End Sub

    End Class

    The advantage would be the compact Property lines.  I wouldn’t expect readonly properties to work with initializers; if you’re making the properties readonly, then I figure you should have to provide an appropriate constructor.  I can see where this would cause problems (e.g. cases where a parameterless constructor is required), but I’m not sure the convoluted syntax in the last example is a good solution.

  3. Kevin Ryall says:

    Read-only auto properties would make the feature MUCH more useful. It’s rare for me to have a list of properties where at least one isn’t read-only (often the immutable ‘reference’ property which is used to override Equals and GetHashCode). Our coding standards require the use of properties instead of public fields in almost all cases (many tools don’t work well with fields), but the code is almost all boilerplate – just getting and setting a member field. Using auto-properties for this would make the code shorter and simpler if it were possible.

    This is not related to read only properties, but the one bit of code we usually have in the property setter (other than the field write) is a single-line null check. This is required for all reference properties (including strings), and prevents us from using automatic properties in many cases. It would be great if I could declare a writeable automatic property which could not be set to null – then I could auto properties in almost all ‘normal’ cases.

    This may mean getting in to the whole are of pre-conditions, and I haven’t looked at the support for those on properties in .NET 4, but I doubt if I can have a non-null  pre-condition on an automatic property?

  4. andr_gin says:

    I think Auto implementing properties are one of the best features of .NET 4.0. You really save much lines of code and much work.

    Unfortunately the current implementation is not very useful at least for me.

    Lets take a look at the coding practise I use.

    Public Class Person

       _FirstName as String

       _LastName as String

       Public Sub New(FirstName As String,LastName As String)

          Me._FirstName = FirstName

          Me._LastName = LastName

      End Sub

       Public Property FirstName As String

          Get

                Return Me._FirstName

         End Get

         Set(value as String)

                Me._FirstName = value

          End Set

     End Property

       Public Property LastName As String

          Get

                Return Me._LastName

         End Get

         Set(value as String)

                Me._LastName= value

          End Set

     End Property

      Public Sub ShowFullName()

             MsgBox(Me._FirstName & " " & Me._LastName)

      End Sub

    End Class

    So there are some coding guidelines that I use (not a company policy, but only mine):

    1.) Access to the members of a class from outside is only done by properties. Some tools do not work well with and I think it is very bad style to have sometimes properties and sometimes direct access to members, even if the syntax is the same in VB.NET.

    2.) All members have a leading underscore in the Name. The properties do not have one.

    3.) All Properties, Private Members and Parameters start with an upper case letter. I know this is a violation of the official coding guidelines, because Parameters should start with lower case letters, but I think it is better readable and as long as I do not develop libraries for external use I think it should not matter.

    4.) From inside the class the backing fields are accessed directly. The only exception are properties for external use that contain some kind of additionl code that is useful in this case (for example checking for valid values in a Setter).

    5.) Every member, Property or Function of the same class is always accessed by using the Me statement. I found out that this drastically decreases typing errors, if you forget the trailing underscore in the constructor. It is also better readable because you see what is a member and what is a local variable.

    This coding guidelines have developed over time, but they work very well for me. The only disadvantage is the need for typing each property. I am currently doing this with snippet  that contains with the shortcut "spro" (pro for property and s for avoiding the use of intellisense instead of my shortcut when I press tab). Then I only have to provide the name of the property and the type. Then I pack all Properties in their own Region to hide them.

    Unfortunately the use of the current auto implemented properties requires me to make some changes:

    1.) There is no official way to access the backing fields. You can still access them, but you dont have IntelliSense support, so I dont think it can be considered as good style. For the average developer that has not followed the 10-4 show and blogs like these it is nearly impossible to even know that there is a backing field with a leading underscore at all. Some developers with a C++ background use the convention of a trailing underscore (FirstName_), even if this does not work well with Intellisense, because you get the suggestion of both the backing field and the property until you type the last character.

    I was told that I should access the property instead. This will be OK for the Getter, but I dont think that this is a good idea in some cases for the Setter.

    Often an auto implemented property is changed to a standard property and additional value checks are done in the Setter.

    For example if you have a triangle one side cannot the longer than the sum of the two other sides:

    Public Property C as Double

        Get

              Return Me._C

        End Get

       Set(value as Double)

            If value > Me._A + Me._B Then

                Throw New ArgumentException("Length of side C cannot be greater than the sum of side a and side b.")

           End If

           Me._C=value

       End Set

    End Property

    This would be a good change of the Triangle class, if you want to do additional checks for invalid values from outside.

    Unfortunately you sometimes need to directly access the backing fields if you want to change more than one value at a time. This can be in a constructor, but also in a load command, if a constructor is not approprieate (for example a filestream that simply opens another file).

    Public Sub New(A as Double,B As Double,C as Double)

       Me.A = A

       Me.B = B

       Me.C = C

    End Sub

    This code will not work, because you cant change Me.A to 5, if B and C are 0.

    The only way to solve this is to provide auto implementing properties called A, B and C and if you want to add the value check, you have to set this property to Private and make an additional property with the value check with another name. This will be the worst condition because it will break code of outside the class and then you have two properties AND a backing field.

    2.) In most cases where you use ReadOnly properties you want to expose the member to outside only for reading, but from inside you want need to write to your members.

    Doing a special trick for the constructor will work for some cases, but for some cases the loading operation is not done in the constructor.

    For example take a FileStream that has a constructor, an Open and a close method:

    Public Class MyFileStream

          Private _FileName As String

          Private _Length as Int64

         Public Sub New(FileName As String)

                Me._Open(FileName)

        End Sub

        Public Sub Open(FileName as String)

              Me._FileName = FileName

              Me._Length = GetLengthOfFile

        End Sub

       Public Sub Close()

             Me._FileName = Nothing

       End Sub

       Public Function GetLengthOfFile()

             Dim FileInformation = DoLongDiskIntensiveIOOperation()

             Return FileInformation.Length

       End Sub

       Public ReadOnly Property FileName As String

          Get

              Return Me._FileName

          End Get

       End Property

    Public ReadOnly Property Length As Int64

          Get

              Return Me._Length

          End Get

       End Property

    End Class

    If you only use tricks for inserting code into the constructor you cannot close the file and load another one.

    On the other side it would be very bad to allow the user to set the Length of the file, because there simply is no way to do this. Length is only used to cache the result of the IO operation.

    I often have cases where I need to cache information that is often used, but needs much time to generate (IO operations or actions with possible side effects must not be placed in a Getter of a property).

    My suggestion to handle Auto implemented properties is to generate an official private backing field that is shown for Intellisense for use inside the class and a public property for use outside the class.

    For a readonly property the private backing field should still be writable from inside, but the public property should only have read access.

    I think that are only a few rare cases where you need a readonly property with a readonly backing field. This can only used for simple structure like classes that only store variables. For more complex classes you should always think of the possibility of reloading data without generating a new instance.

    Imagine all the little Windows Dektop application with a main startup Form. Here most information is not generated in the constructor, but only when loading the Form in the Form.Load event. In the Form_Load handler you need to stick with this instance of the Form and can not simply return a new instance with the right values.

  5. weitzhandler says:

    This is an interesting issue.

    You do need a way to specify mixed access levels in a property, consider a property that has a private setter but a protected getter, I would say it should be declared

    Public ReadOnly Property MyProperty As String

    generated:

    Private _MyProperty As String

    Public ReadOnly Property MyProperty As String

    Get

      Return _MyProperty

    End Get

    Private Set(value As String)

      _MyProperty = value

    End Set

    End Property

    And vice versa.