dotnet / vblang

The home for design of the Visual Basic .NET programming language and runtime library.
289 stars 65 forks source link

[Proposal] Customized Auto Properties #574

Open VBAndCs opened 4 years ago

VBAndCs commented 4 years ago

I introduced many suggestions to shorten properties syntax, as they are the largest VB blocks. Auto properties are grate, but there are many cases where they can't be used. Here is one of them: I had to write the full property body, just to call a sub in the Set section:

Dim _account As Account
Public Property Account As Account
   Get
      Return _account
   End Get
   Set(value As Account)
      _account = value
      Refresh()
   End Set
End Property

I suggest to allow to append some code to auto implemented properties, as this:

Public Property Account As Account
On Set
   Refresh()
End Set

which will be lowered to the first example. and in case of one statement set, it can be reduced to:

Public Property Account As Account On Set: Refresh()

The same can be done with On Get if needed. There is no need to use both together, as in this it will be logical to use the normal full property body, and I had a previous proposal to make it shorter for a single-line Get or Set.

gilfusion commented 4 years ago

Would you be able to control if the assignment to the backing field comes before or after the On Set? It seems there may be reasons or opinions to put it either way, like validation code (before the assignment) or raising some event (after, like your refresh example above).

VBAndCs commented 4 years ago

@gilfusion We always have the normal full body to solve any unusual case.

Alternative suggestion: Suppose we have this property:

    Dim _account As Account
    Public Property Account As Account
        Get
            Return _account
        End Get
        Set(value As Account)
            _account = value
            lblAccountName.Content = _account.DisplayName
            Refresh()
        End Set
    End Property

I suggest to write it as half auto:

    Public Property Account As Account
        Set(value As Account)
            _account = value
            lblAccountName.Content = _account.DisplayName
            Refresh()
        End Set
    End Property

Sense there is no WriteOnly keyword, the Get accessory can be considered auto. And even more compact

Public Property.Set Account As Account
   _account = value
   lblAccountName.Content = _account.DisplayName
   Refresh()
End Property.Set

And we can take out the _account = value

Public Property.Set Account As Account
   lblAccountName.Content = value.DisplayName
   Refresh()
End Property.Set

so, the 11-lines property becomes only 4 lines!

rskar-git commented 4 years ago

... so, the 11-lines property becomes only 4 lines!

Wow!, that's like 2 lines better than current VB.NET! :)

Public Property Account As Account
    Get
        Return _account : End Get
    Set
        _account = Value : lblAccountName.Content = _account.DisplayName : Refresh() : End Set
End Property

My friend, you are too concerned about the "code golf"! (https://www.barrymichaeldoyle.com/code-golf/, https://medium.com/better-programming/have-you-heard-of-code-golf-f410c8692dbc, https://softwareengineering.stackexchange.com/questions/43151/should-you-sacrifice-code-readability-with-how-efficient-code-is)

What you're aiming for is some sort of automatic backing (e.g. _account), but with "events". However, we need to be careful to not introduce any "magic code" anti-patterns. I.e. it needs to be clear how the backing is done, how the validations are done, and how any side-effects may happen.

Changing the backing is the easy part. The hard part is in the validation and side-effects - things get quite messy. In the end, I don't like it enough to be worth the trouble. But in any case, I'll put my version of this proposal in the next post.

rskar-git commented 4 years ago

Another way to do this (which I don't necessarily like any better)...

BACKING FIELD

First the backing. For a variable already declared, indicate that with the With clause:

Dim _account As Account
Public Property Account As Account With _account

If the variable is not declared, indicate that with the Using clause:

Public Property Account As Account Using _account

The With versus Using signals intent, to avoid accidental field usage. Using declares a private variable as explicitly named. Note that a single-line Property can still be done with With and Using.

VALIDATION

Next is the validation. That could be indicated with an optional When clause. If its condition is True, the value assignment (Set) or return (Get) may then be done. For Set, the assignment is the implied first statement; and for Get, the return is the implied last statement.

Here's where the awkwardness starts: What to do if the When condition is False? We can't allow for a silent fail; maybe some sort of Exception needs to be thrown (like InvalidOperationException)? Perhaps the When could have an optional Else - but then what? It still seems like an Exception must still happen.

Anyway, here's what your example could look like:

Public Property Account As Xyz.Account Using _account
    Get
        ' There is no When clause, so Get body is always executed.
        ' The implied final statement is Return _account; it can be
        ' avoided by earlier Return or Exit Property statement.
    End Get
    Set(value As Account) When value.Balance > 0
        ' Set body is executed only if When condition is True.
        ' The implied first statement is _account = value;
        ' which of course does not happen if When condition is False.
        lblAccountName.Content = _account.DisplayName
        Refresh()
    End Set
End Property

In the context of With or Using, the Get and Set blocks can now be optional. However, if validation is needed via When, then the block is needed. Since there is no When on the above Get, it can be removed:

Public Property Account As Xyz.Account Using _account
    Set(value As Account) When value.Balance > 0
        lblAccountName.Content = _account.DisplayName
        Refresh()
    End Set
End Property

Also, since VB already allows for the Set parameter to be automatic, are golfing score can now be reduced by 18 characters:

Public Property Account As Xyz.Account Using _account
    Set When value.Balance > 0
        lblAccountName.Content = _account.DisplayName
        Refresh()
    End Set
End Property

RECAP

(1) With and Using clause to indicate backing. When used, Property can be a single line, working similarly to how it now works with automatic backing.

(2a) Optional When expression for validation. Assignment/Return and Block is executed if True; otherwise optional Else statement is executed and then exception is thrown.

(2b) Missing When works the same as When True.

(3a) In Set, assignment is implied first statement of block.

(3b) In Get, return is implied last statement of block.

(4a) If Set not given, implied block of assignment.

(4b) If Get not given, implied block of return.

(5) If there is neither a Set or Get block, then Property must be a single-line (as it is today).

pricerc commented 4 years ago

Sense there is no WriteOnly keyword

??

Yes, there is:

Sub Main
    Boo = "Hello, World!"
End Sub

Writeonly Property Boo As String
    Set
        Console.WriteLine(value)
    End Set
End Property
pricerc commented 4 years ago

too concerned about the "code golf"

I hadn't seen the term before, but it explains a lot, and I do believe it to be a curse anywhere other than in an obfuscated code competition.

I have seen comments from people (not in this forum) who believe that C# is faster than VB because there are fewer characters.

I think some believe that all code is actually interpreted at run time, and don't understand how a compiler works.

pricerc commented 4 years ago

I actually think there is some merit in what this proposal aims to achieve, but I think a more complete package, like @AnthonyDGreen was proposing in #282, or his earlier #194

rskar-git commented 4 years ago

@pricerc, Yep I agree that something like #282 is the better approach.

VBAndCs commented 4 years ago

@rskar-git

Public Property Account As Account
    Get
        Return _account : End Get
    Set
        _account = Value : lblAccountName.Content = _account.DisplayName : Refresh() : End Set
End Property

even better:

Public Property Account As Account :  Get : Return _account : End Get : Set :  _account = Value : lblAccountName.Content = _account.DisplayName : Refresh() : End Set : End Property

@pricerc

Sense there is no WriteOnly keyword

in the code sample. missing WriteOnly is an indication to the language that this is a customized auto property.

pricerc commented 4 years ago

in the code sample. missing WriteOnly is an indication to the language that this is a customized auto property.

That makes a bit more sense.

Still, I think this is horrible:

Public Property.Set Account As Account
   lblAccountName.Content = value.DisplayName
   Refresh()
End Property.Set

It's almost like a VB6 "Property Set", only less clear.

In one of the articles that @rskar-git provided, there is this quote attributed to Wes Dyer (of the C# development team) :

make it correct, make it clear, make it concise, make it fast.

In that order.

In my view, this proposal would reduce clarity in an effort to make the code more concise. That is by definition, a bad thing.

jrmoreno1 commented 4 years ago

I think #194 and two new interfaces (one including all properties unless excluded by an attribute, one only including properties with an attribute) and a method that implemented the interface would be fine, or even better a method with the right signature and a (new syntax) handles clause (* or explicit list).

Public Class Notify
   Public Property Here as String
   Public Property There as Integer

   Public Sub OnCompilerMagic(Of T)(propertyName As String, 
                                                     ByRef backingField As T, 
                                                     ByRef value As T) Handles Here, There
         If backingField
    End Sub

End Class