Open AdamSpeight2008 opened 7 years ago
I find these types of expressions to be far less readable than a normal method/property. I realise that it's subjective though.
As for less typing, the IDE already does most of the work for you. You just have to type Get
, press enter, and the IDE will insert the two End
statements.
I'm hesitant to add new syntax to support the scenario of property stubs for properties you just haven't implemented yet. Can you provide more examples?
@AnthonyDGreen Is this not just a VB equivalent of C# 7's expression bodied members and throw expressions? I think the original code shown (throwing NotImplementedException
) was just as an example of the syntax.
NotImplementedException
is already provide via auto-completion on overriddes
property.
Public MustInherit Class Base
MustOverride ReadOnly Property Value As Int32
Overridable ReadOnly Property Text As String = "Base"
End Class
Public Class F : Inherits Base
Public Overrides ReadOnly Property Value As Integer
Get
Throw New NotImplementedException()
End Get
End Property
End Class
If we could express that like auto-implemented properties.
Public Class F : Inherits Base
Public Overrides ReadOnly Property Value As Integer => Throw New NotImplementedException()
End Class
If we override the Value
property, auto-completion provides the simple call to the base implementaion.
Public Class F : Inherits Base
Public Overrides ReadOnly Property Text As String => "First"
Public Overrides ReadOnly Property Value As Integer => Throw New NotImplementedException()
End Class
12 Loc down to 4 Loc is a big reduction of syntax ceremony, and it just a simple to read. IMHO
I've refactored SyntaxNode as an real-world example utilising this feature.
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Runtime.CompilerServices
Imports Microsoft.CodeAnalysis.Syntax.InternalSyntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax
<DebuggerDisplay("{GetDebuggerDisplay(), nq}")>
Partial Friend Class VisualBasicSyntaxNode
Inherits GreenNode
Friend ReadOnly Property Kind As SyntaxKind => Return CType(Me.RawKind, SyntaxKind)
Friend ReadOnly Property ContextualKind As SyntaxKind => Return CType(Me.RawContextualKind, SyntaxKind)
Public Overrides ReadOnly Property KindText As String => Return Me.Kind.ToString()
Public Overrides ReadOnly Property Language As String => Return LanguageNames.VisualBasic
''' <summary>Should only be called during construction.</summary>
''' <remarks>This should probably be an extra constructor parameter, but we don't need more constructor overloads.</remarks>
Protected Sub SetFactoryContext(context As ISyntaxFactoryContext)
If context.IsWithinAsyncMethodOrLambda Then SetFlags(NodeFlags.FactoryContextIsInAsync)
If context.IsWithinIteratorContext Then SetFlags(NodeFlags.FactoryContextIsInIterator)
End Sub
Friend Function MatchesFactoryContext(context As ISyntaxFactoryContext) As Boolean
Return context.IsWithinAsyncMethodOrLambda = Me.ParsedInAsync AndAlso
context.IsWithinIteratorContext = Me.ParsedInIterator
End Function
#Region "Serialization"
Friend Sub New(reader As ObjectReader)
MyBase.New(reader)
End Sub
#End Region
' The rest of this class is just a convenient place to put some helper functions that are shared by the
' various subclasses.
Public Overrides ReadOnly Property IsStructuredTrivia As Boolean => Return TypeOf Me Is StructuredTriviaSyntax
Public Overrides ReadOnly Property IsDirective As Boolean => Return TypeOf Me Is DirectiveTriviaSyntax
Public Overrides ReadOnly Property IsSkippedTokensTrivia As Boolean => Return Me.Kind = SyntaxKind.SkippedTokensTrivia
Public Overrides ReadOnly Property IsDocumentationCommentTrivia As Boolean => Return Me.Kind = SyntaxKind.DocumentationCommentTrivia
Protected Overrides Function GetSlotCount() As Integer
Throw ExceptionUtilities.Unreachable
End Function
Protected Property _slotCount As Integer
Get
Return Me.SlotCount
End Get
Set(value As Integer)
Me.SlotCount = value
End Set
End Property
Friend Function GetFirstToken() As SyntaxToken
Return DirectCast(Me.GetFirstTerminal(), SyntaxToken)
End Function
Friend Function GetLastToken() As SyntaxToken
Return DirectCast(Me.GetLastTerminal(), SyntaxToken)
End Function
' Get the leading trivia a green array, recursively to first token.
Friend Overridable Function GetLeadingTrivia() As GreenNode
Dim possibleFirstChild = GetFirstToken()
If possibleFirstChild Is Nothing Then Return Nothing
Return possibleFirstChild.GetLeadingTrivia()
End Function
Public Overrides Function GetLeadingTriviaCore() As GreenNode
Return Me.GetLeadingTrivia()
End Function
' Get the trailing trivia a green array, recursively to first token.
Friend Overridable Function GetTrailingTrivia() As GreenNode
Dim possibleLastChild = GetLastToken()
If possibleLastChild Is Nothing Then Return Nothing
Return possibleLastChild.GetTrailingTrivia()
End Function
Public Overrides Function GetTrailingTriviaCore() As GreenNode
Return Me.GetTrailingTrivia()
End Function
Protected Sub New(kind As SyntaxKind)
MyBase.New(CType(kind, UInt16))
GreenStats.NoteGreen(Me)
End Sub
Protected Sub New(kind As SyntaxKind, width As Integer)
MyBase.New(CType(kind, UInt16), width)
GreenStats.NoteGreen(Me)
End Sub
Protected Sub New(kind As SyntaxKind, errors As DiagnosticInfo())
MyBase.New(CType(kind, UInt16), errors)
GreenStats.NoteGreen(Me)
End Sub
Protected Sub New(kind As SyntaxKind, errors As DiagnosticInfo(), width As Integer)
MyBase.New(CType(kind, UInt16), errors, width)
GreenStats.NoteGreen(Me)
End Sub
Friend Sub New(kind As SyntaxKind, diagnostics As DiagnosticInfo(), annotations As SyntaxAnnotation())
MyBase.New(CType(kind, UInt16), diagnostics, annotations)
GreenStats.NoteGreen(Me)
End Sub
Friend Sub New(kind As SyntaxKind, diagnostics As DiagnosticInfo(), annotations As SyntaxAnnotation(), fullWidth As Integer)
MyBase.New(CType(kind, UInt16), diagnostics, annotations, fullWidth)
GreenStats.NoteGreen(Me)
End Sub
''' <summary>
''' Get all syntax errors associated with this node, or any child nodes, grand-child nodes, etc. The errors
''' are not in order.
''' </summary>
Friend Overridable Function GetSyntaxErrors() As IList(Of DiagnosticInfo)
If Not ContainsDiagnostics Then Return Nothing
Dim accumulatedErrors As New List(Of DiagnosticInfo)
AddSyntaxErrors(accumulatedErrors)
Return accumulatedErrors
End Function
Friend Overridable Sub AddSyntaxErrors(accumulatedErrors As List(Of DiagnosticInfo))
If Me.GetDiagnostics IsNot Nothing Then
accumulatedErrors.AddRange(Me.GetDiagnostics)
End If
Dim cnt = SlotCount()
If cnt = 0 Then Return
For i As Integer = 0 To cnt - 1
Dim child = GetSlot(i)
If child IsNot Nothing AndAlso child.ContainsDiagnostics Then
DirectCast(child, VisualBasicSyntaxNode).AddSyntaxErrors(accumulatedErrors)
End If
Next
End Sub
Private Function GetDebuggerDisplay() As String
Dim text = ToFullString()
If text.Length > 400 Then text = text.Substring(0, 400)
Return Kind.ToString & ":" & text
End Function
Friend Overloads Shared Function IsEquivalentTo(left As VisualBasicSyntaxNode, right As VisualBasicSyntaxNode) As Boolean
If left Is right Then Return True
If left Is Nothing OrElse right Is Nothing Then Return False
Return left.IsEquivalentTo(right)
End Function
' Use conditional weak table so we always return same identity for structured trivia
Private Shared ReadOnly s_structuresTable As New ConditionalWeakTable(Of SyntaxNode, Dictionary(Of Microsoft.CodeAnalysis.SyntaxTrivia, SyntaxNode))
Public Overrides Function GetStructure(trivia As Microsoft.CodeAnalysis.SyntaxTrivia) As SyntaxNode
If Not trivia.HasStructure Then Return Nothing
Dim parent = trivia.Token.Parent
If parent Is Nothing Then Return VisualBasic.Syntax.StructuredTriviaSyntax.Create(trivia)
Dim [structure] As SyntaxNode = Nothing
Dim structsInParent = s_structuresTable.GetOrCreateValue(parent)
SyncLock structsInParent
If Not structsInParent.TryGetValue(trivia, [structure]) Then
[structure] = VisualBasic.Syntax.StructuredTriviaSyntax.Create(trivia)
structsInParent.Add(trivia, [structure])
End If
End SyncLock
Return [structure]
End Function
Public Overrides Function CreateSeparator(Of TNode As SyntaxNode)(element As SyntaxNode) As CodeAnalysis.SyntaxToken
Dim separatorKind As SyntaxKind = SyntaxKind.CommaToken
If element.Kind = SyntaxKind.JoinCondition Then
separatorKind = SyntaxKind.AndKeyword
End If
Return VisualBasic.SyntaxFactory.Token(separatorKind)
End Function
Public Overrides Function IsTriviaWithEndOfLine() As Boolean
Return Me.Kind = SyntaxKind.EndOfLineTrivia OrElse Me.Kind = SyntaxKind.CommentTrivia
End Function
End Class
End Namespace
And SyntaxLiterals
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax
Friend Partial Class CharacterLiteralTokenSyntax
Friend NotOverridable Overrides ReadOnly Property ObjectValue As Object => Return Me.Value
End Class
Friend Partial Class DateLiteralTokenSyntax
Friend NotOverridable Overrides ReadOnly Property ObjectValue As Object => Return Me.Value
End Class
Friend Partial Class DecimalLiteralTokenSyntax
Friend NotOverridable Overrides ReadOnly Property ObjectValue As Object => Return Me.Value
End Class
Friend Partial Class StringLiteralTokenSyntax
Friend NotOverridable Overrides ReadOnly Property ObjectValue As Object => Return Me.Value
Friend NotOverridable Overrides ReadOnly Property ValueText As String => Return Me.Value
End Class
Friend NotInheritable Class IntegerLiteralTokenSyntax(Of T)
Inherits IntegerLiteralTokenSyntax
Friend ReadOnly _value As T
Friend Sub New(kind As SyntaxKind, text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, base As LiteralBase, typeSuffix As TypeCharacter, value As T)
MyBase.New(kind, text, leadingTrivia, trailingTrivia, base, typeSuffix)
Me._value = value
End Sub
Friend Sub New(kind As SyntaxKind, errors As DiagnosticInfo(), annotations As SyntaxAnnotation(), text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, base As LiteralBase, typeSuffix As TypeCharacter, value As T)
MyBase.New(kind, errors, annotations, text, leadingTrivia, trailingTrivia, base, typeSuffix)
Me._value = value
End Sub
Friend Sub New(reader As ObjectReader)
MyBase.New(reader)
Me._value = CType(reader.ReadValue(), T)
End Sub
Shared Sub New()
ObjectBinder.RegisterTypeReader(GetType(IntegerLiteralTokenSyntax(Of T)), Function(r) New IntegerLiteralTokenSyntax(Of T)(r))
End Sub
Friend Overrides Sub WriteTo(writer As ObjectWriter)
MyBase.WriteTo(writer)
writer.WriteValue(Me._value)
End Sub
''' <summary>The value of the token.</summary>
Friend ReadOnly Property Value As T => Return Me._value
Friend Overrides ReadOnly Property ValueText As String => Return _value.ToString
Friend Overrides ReadOnly Property ObjectValue As Object => Return Me.Value
Public Overrides Function WithLeadingTrivia(trivia As GreenNode) As GreenNode
Return New IntegerLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, GetAnnotations, Text, trivia, GetTrailingTrivia, _base, _typeSuffix, _value)
End Function
Public Overrides Function WithTrailingTrivia(trivia As GreenNode) As GreenNode
Return New IntegerLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, GetAnnotations, Text, GetLeadingTrivia, trivia, _base, _typeSuffix, _value)
End Function
Friend Overrides Function SetDiagnostics(newErrors As DiagnosticInfo()) As GreenNode
Return New IntegerLiteralTokenSyntax(Of T)(Kind, newErrors, GetAnnotations, Text, GetLeadingTrivia, GetTrailingTrivia, _base, _typeSuffix, _value)
End Function
Friend Overrides Function SetAnnotations(annotations As SyntaxAnnotation()) As GreenNode
Return New IntegerLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, annotations, Text, GetLeadingTrivia, GetTrailingTrivia, _base, _typeSuffix, _value)
End Function
End Class
''' <summary>
''' Represents an integer literal token.
''' </summary>
Friend MustInherit Class IntegerLiteralTokenSyntax
Inherits SyntaxToken
Friend ReadOnly _base As LiteralBase
Friend ReadOnly _typeSuffix As TypeCharacter
Friend Sub New(kind As SyntaxKind, text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, base As LiteralBase, typeSuffix As TypeCharacter)
MyBase.New(kind, text, leadingTrivia, trailingTrivia)
Me._base = base
Me._typeSuffix = typeSuffix
End Sub
Friend Sub New(kind As SyntaxKind, errors As DiagnosticInfo(), annotations As SyntaxAnnotation(), text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, base As LiteralBase, typeSuffix As TypeCharacter)
MyBase.New(kind, errors, annotations, text, leadingTrivia, trailingTrivia)
Me._base = base
Me._typeSuffix = typeSuffix
End Sub
Friend Sub New(reader As ObjectReader)
MyBase.New(reader)
Me._base = CType(reader.ReadByte(), LiteralBase)
Me._typeSuffix = CType(reader.ReadByte(), TypeCharacter)
End Sub
Friend Overrides Sub WriteTo(writer As ObjectWriter)
MyBase.WriteTo(writer)
writer.WriteByte(CType(Me._base, Byte))
writer.WriteByte(CType(Me._typeSuffix, Byte))
End Sub
''' <summary>Whether the token was specified in base 10, 16, 8, or 2.</summary>
Friend ReadOnly Property Base As LiteralBase => Return Me._base
''' <summary>
''' The type suffix or type character that was on the literal, if any. If no suffix
''' was present, TypeCharacter.None is returned.
''' </summary>
Friend ReadOnly Property TypeSuffix As TypeCharacter => Return Me._typeSuffix
End Class
''' <summary>
''' Represents an floating literal token.
''' </summary>
Friend NotInheritable Class FloatingLiteralTokenSyntax(Of T)
Inherits FloatingLiteralTokenSyntax
Friend ReadOnly _value As T
Friend Sub New(kind As SyntaxKind, text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, typeSuffix As TypeCharacter, value As T)
MyBase.New(kind, text, leadingTrivia, trailingTrivia, typeSuffix)
Me._value = value
End Sub
Friend Sub New(kind As SyntaxKind, errors As DiagnosticInfo(), annotations As SyntaxAnnotation(), text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, typeSuffix As TypeCharacter, value As T)
MyBase.New(kind, errors, annotations, text, leadingTrivia, trailingTrivia, typeSuffix)
Me._value = value
End Sub
Friend Sub New(reader As ObjectReader)
MyBase.New(reader)
Me._value = CType(reader.ReadValue(), T)
End Sub
Shared Sub New()
ObjectBinder.RegisterTypeReader(GetType(FloatingLiteralTokenSyntax(Of T)), Function(r) New FloatingLiteralTokenSyntax(Of T)(r))
End Sub
Friend Overrides Sub WriteTo(writer As ObjectWriter)
MyBase.WriteTo(writer)
writer.WriteValue(Me._value)
End Sub
''' <summary>The value of the token.</summary>
Friend ReadOnly Property Value As T => Return Me._value
Friend Overrides ReadOnly Property ValueText As String => Return _value.ToString
Friend Overrides ReadOnly Property ObjectValue As Object => Return Me.Value
Public Overrides Function WithLeadingTrivia(trivia As GreenNode) As GreenNode
Return New FloatingLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, GetAnnotations, Text, trivia, GetTrailingTrivia, _typeSuffix, _value)
End Function
Public Overrides Function WithTrailingTrivia(trivia As GreenNode) As GreenNode
Return New FloatingLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, GetAnnotations, Text, GetLeadingTrivia, trivia, _typeSuffix, _value)
End Function
Friend Overrides Function SetDiagnostics(newErrors As DiagnosticInfo()) As GreenNode
Return New FloatingLiteralTokenSyntax(Of T)(Kind, newErrors, GetAnnotations, Text, GetLeadingTrivia, GetTrailingTrivia, _typeSuffix, _value)
End Function
Friend Overrides Function SetAnnotations(annotations As SyntaxAnnotation()) As GreenNode
Return New FloatingLiteralTokenSyntax(Of T)(Kind, GetDiagnostics, annotations, Text, GetLeadingTrivia, GetTrailingTrivia, _typeSuffix, _value)
End Function
End Class
''' <summary>Represents an floating literal token.</summary>
Friend MustInherit Class FloatingLiteralTokenSyntax
Inherits SyntaxToken
Friend ReadOnly _typeSuffix As TypeCharacter
Friend Sub New(kind As SyntaxKind, text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, typeSuffix As TypeCharacter)
MyBase.New(kind, text, leadingTrivia, trailingTrivia)
Me._typeSuffix = typeSuffix
End Sub
Friend Sub New(kind As SyntaxKind, errors As DiagnosticInfo(), annotations As SyntaxAnnotation(), text As String, leadingTrivia As GreenNode, trailingTrivia As GreenNode, typeSuffix As TypeCharacter)
MyBase.New(kind, errors, annotations, text, leadingTrivia, trailingTrivia)
Me._typeSuffix = typeSuffix
End Sub
Friend Sub New(reader As ObjectReader)
MyBase.New(reader)
Me._typeSuffix = CType(reader.ReadByte(), TypeCharacter)
End Sub
Friend Overrides Sub WriteTo(writer As ObjectWriter)
MyBase.WriteTo(writer)
writer.WriteByte(CType(Me._typeSuffix, Byte))
End Sub
''' <summary>
''' The type suffix or type character that was on the literal, if any. If no suffix
''' was present, TypeCharacter.None is returned.
''' </summary>
Friend ReadOnly Property TypeSuffix As TypeCharacter => Return Me._typeSuffix
End Class
End Namespace
Maybe it's because I'm already familiar with this from C#, but I see the two refactored examples @AdamSpeight2008 did as being more readable/understandable/concise. Particularly in the case of ReadOnly
properties, it makes the amount of code you have to write almost trivial, certainly easier to express the intent.
@AdamSpeight2008 Using vb With
keyword make this simple expression more vb style than using =>
, example: https://github.com/dotnet/roslyn/issues/15708
If not implemented, then we can write this simple expression:
Public ReadOnly Property Example As Foo With New NotImplementedException
@xieguigang
My feelings is that could issues it object initializers
and with blocks
@xieguigang @AdamSpeight2008
I'm OK with =>
, but instead of With
, would it be more VB-ish to use Get
?:
Friend Overrides ReadOnly Property CanConstruct As Boolean Get Throw New NotSupportedException()
Friend ReadOnly Property Kind As SyntaxKind Get Return CType(Me.RawKind, SyntaxKind)
It's a subtle point, but =>
is sort-of the same as >=
(although the IDE will transform => into >=).
So, instead of =>
how about colon?:
Friend Overrides ReadOnly Property CanConstruct As Boolean : Throw New NotSupportedException()
Friend ReadOnly Property Kind As SyntaxKind : Return CType(Me.RawKind, SyntaxKind)
@rskar-git Would the compiler complain ?
Friend Overrides ReadOnly Property CanConstruct As Boolean Get Throw New NotSupportedException()
Friend ReadOnly Property Kind As SyntaxKind Get Return CType(Me.RawKind, SyntaxKind)
about incomplete Get ... End Get
block?
Friend Overrides ReadOnly Property CanConstruct As Boolean
Get
Throw New NotSupportedException()
End Get
End Property
Friend ReadOnly Property Kind As SyntaxKind
Get
Return CType(Me.RawKind, SyntaxKind)
End Get
End Property
@AdamSpeight2008
Would the compiler complain ... about incomplete
Get ... End Get
block?
(I hope I'm not misunderstanding your question.) I was merely suggesting Get
in place of =>
, all else stays the same, and so there is still no End Get
in this case.
The Get
of a proper Get ... End Get
block must start on a new line; putting the Get
on the same line as the ReadOnly Property
definition already results in a syntax error. (Unless that's been changed in VS 2017.) So I wouldn't anticipate inventing a new syntax where Get
is on the same line as ReadOnly Property
should make for any kind of trouble.
But maybe I'm being naive. It just seems to me that ReadOnly Property ... Get <simple-expression>
is somewhat analogous to the two modes of If ... Then
or the new With
syntax (New Foo() With { ... }
) as far as syntax parsing may go. Is there something to the compiler's design that would make the use of Get
in this way (as an alternative to =>
) problematic?
As I said before, I'm OK with =>
, but it would seem a rather lonely operator if `ReadOnly Property ... =>
@rskar-git
My thinking on =>
is that would be easier to parse, ( a single char character lookup at =
) rather than parsing a Get ... End Get
block only to having it fail and require it to be reparsed as a inline-get
.
@AdamSpeight2008
It's clear now I need to really get familiar with how the VB compiler actually works; I'm (not yet) at any place to say whether or not a reparse would be likely.
But you've got me wondering ... is that what happens now for New Foo() With { ... }
? I.e., does the compiler first fail at finding an expected End With
at some point then back-tracks in order to parse "with-clause-of-new-statement"?
Would it possible to support writing as well, i think it would allow simpler implementations of various patterns.
Public ReadOnly Property Value As String => m_object.Value
Public Property Value As String => m_object.Value
Public ReadOnly Property Value As String => Throw New Exception()
Public Property Value As String => Throw New Exception()
Public Overrides ReadOnly Property Value As Integer => 2
Public Overrides Property Value As Integer => 2 ' Error
Private m_LastName As String = ""
Public Property FirstName As String => Me.Value("FirstName")
Public Property LastName As String => Me.Value(m_LastName)
Public Property Email As String => Me.Value(NameOf(Me.Email))
Public Property BirthDate As Date => Me.Value()
Public Enum Data
Address
Email
Phone
End Enum
Class Customer
Inherits Model
Private m_Id As Integer
Private m_FirstName As Integer
Property Id As Integer => MyBase.Value(m_Id)
Property FirstName As String => MyBase.SetOrGet(m_FirstName)
Property LastName As String => MyBase.Value
Property BirthDate As Date => MyBase.Value
Property Address As String => MyBase.Value(Data.Addres)
Property Email As String => MyBase.Value(Data.Email)
Property Phone As String => MyBase.Value(Data.Phone)
End Class
Class Model
Public Property Value(Of T)(<CallerMemberName> propertyName As String) As T
...
End Property
Public Property Value(Of T)(data As Data) As T
...
End Property
Private Property Value(Of T)(ByRef field As T, ByVal value) As T
...
End Function
'Or something like this since ByRef is not supported in properties
Private Sub SetOrGet(Of T)(ByRef field As T, value As T, isGet As Boolean ) As T
End Sub
End Class
@edin I've not looked into supporting Write
, as I not thought out how to state the value
being passed to the property.
Public Property Value As T
Get
Return _Value
End Get
' Eg the 'newValue parameter of the setter
Set( newValue As T )
_Value = newValue
End Set
End Property
Some languages reserve the name value
to be used for the role. Doing that to VB would very predominately a breaking change. So need more thought time on it.
If you found a way, please don't be a scared to post your idea. As it may be the one we need.
Some languages reserve the name value to be used for the role.
@AdamSpeight2008 Actually, VB already supports that. The argument list for a set accessor is optional. If it's left off, Value
is assumed to be the argument.
Public Property Foo As Integer
Get
Return _foo
End Get
Set
_foo = Value
End Set
End Property
And yes, it still works if the property is called Value
. 😄
@AdamSpeight2008
Here is more details how could this works:
Following syntax
Public Property Name As String => {Expression}
would be expanded to get and optinaly set depending if it's possible to construct assigment for given expression. When ReadOnly is specified then setter will not be generated. In case that it's not possible to construct assigment to the expression e.g. constant value, then property needs to be ReadOnly.
(Instead of => maybe it could be used just = sign.)
Public Property Name As String => {Expression}
Get
Return {Expression}
End Get
Set(value As String)
{Expression} = value
End Set
End Property
' In case of throw expression
Public Property Name As String => {ThrowExpression}
Get
{ThrowExpression}
End Get
Set(value As String)
{ThrowExpression}
End Set
End Property
Here are some more examples how it could be used.
Class Customer
Public Property ID As Integer
Public Property Name As String
End Class
Class Animal Public Property ID As Integer Public Property Name As String End Class
Class CustomerView Private m_Customer As Customer Private m_Animal As Animal
Public ReadOnly Property CustomerID As Integer => m_Customer.ID
Public ReadOnly Property AnimalID As Integer => m_Animal.ID
Public Property CustomerName As String => m_Customer.Name
Public Property AnimalName As String => m_Animal.Name
End Class
An extension to this would be to add support for chaining properties, but generic indexed properties and byref parameters are not currently supported so i guess it would be lot's of work.
- Custom property indexer is used to implement common logic for multiple properties
```vb
Class Customer
Private m_ID As Integer
Private m_FirstName As String
Private m_LastName As String
Public ReadOnly Property Id As Integer => CustomIndexer(m_ID)
Public Property FirstName As String => CustomIndexer(m_FirstName)
Public Property LastName As String => CustomIndexer(m_LastName)
' More properties
Public Property IsChanged As Boolean
Private Property CustomIndexer(Of T) (ByRef field As T) As T
Get
return field
End Get
Set(value As T)
field = value
IsChanged = True
End Set
End Property
End Class
Class Customer
Public Property ID As Integer
Public Property FirstName As String
Public Property LastName As String
...
End Class
Class CustomerProxy
Private m_Customer As Customer
Public Property FirstName As String => PropIndexer(m_Customer.FirstName)
Public Property LastName As String => PropIndexer(m_Customer.LastName)
Private Property PropIndexer(Of T) (prop As PropertyExpression) As T
Get
Return CType(prop.GetValue() , T)
End Get
Set(value As T)
prop.SetValue(value)
End Set
End Property
End Class
Typed access for common dictionary entries
Class Customer
Private m_Map As New Dictionary(Of String, Object)
Public Property Id As Id => CustomIndexer()
Public Property FirstName As String => CustomIndexer()
Public Property LastName As String => CustomIndexer()
Private Property CustomIndexer(Of T) (<CallerMemberName> name As String = Nothing) As T
Get
Return CType(m_Map(name), T)
End Get
Set
m_Map(name) = value
End Set
End Property
End Class
Typed data row
Class Customer
Private m_DataRow As DataRow
Public Property Id As Id => DataRow("col_ID")
Public Property FirstName As String => DataRow("col_FirstName")
Public Property LastName As String => DataRow("col_LastName")
Private Property DataRow(Of T) (name as String) As T
Get
Return CType(m_DataRow(name), T)
End Get
Set
m_DataRow(name) = value
End Set
End Property
End Class
Highly recommend using =>
or similar (or even Get =>
, or Get =
) as opposed to just Get
(or With
): (reasons and examples below)
Friend Overrides ReadOnly Property CanConstruct As Boolean Get Throw New NotSupportedException()
looks like just a long string of keywords; it isn't obvious where the definition expression begins. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members
=>
is too C#-like,
feel free to use a simple =
. That's how it's done in Scala for instance: def myMethod(x:Int):Int = 5 + x
For properties you would need to add Get
(as I wrote above) because otherwise it's simply an initialized shorthand property. Public Property myProp As Integer = 5
(This doesn't apply to Scala. as setters are a different ballgame there.)Public Property myProp As Integer Get = 5
Public Property myProp As Integer Get => 5
Public Property myProp As Integer => 5
Public Function myMethod() As Integer => 5
I cannot say enough how much this feature would mean to me, in expressing 'computed properties' / expressions concisely in data classes -- makes a big difference as far as being able to split them up and compose them together. Hoping we can get a timeline on this from the VB team.
(In general features that are already in C# are a good candidate for inclusion in VB, barring reasons specifically not to, correct? And it's just a question of resources?)
I have a feeling that =>
is going to be the winning symbol, especially as we explore in what ways in can be employed in parallel with how it's used in C#.
So, if I'm understanding things right, then:
ReadOnly Property myProp As T => (any expression which returns a value; or Throw exception)
WriteOnly Property myProp As T => (any statement or function to make assignment via parameter Value; or Throw exception)
Property myProp As T => (something assignable, such as field or property, and must also be read-able; or Throw exception)
What if hooks to events or interfaces necessary? Perhaps do Handles
and Implements
before =>
.
Property myProp As T Implements IFoo.FooProp => myStruct.myStructField
Sub Bar() Handles Foo.Boom => Throw New Exception("Boom!")
Add to that as many adaptations of C# usage of =>
as possible.
Anyway, below is hypothetical VB syntax to consider.
' Adaptations of examples from:
' https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members
' ==== Methods
Imports System
Public Class Person
Public Sub New Person(firstName As String, lastName As String)
fname = firstName
lname = lastName
End Sub
Private fname As String
Private lname As String
Public Function ToString() As String => $"{fname} {lname}".Trim()
Public Sub DisplayName() => Console.WriteLine(ToString())
End Class
Class Example
Shared Sub Main()
Dim p As New Person("Mandy", "Dejesus")
Console.WriteLine(p)
p.DisplayName()
End Sub
End Class
'==== Constructors, and
'==== Property get statements, and
'==== Property set statements
Public Class Location
Private locationName As String
' This one may be tricky - include auto-generated call to MyBase.New()?
Public Sub New(name As String) => locationName = name
Public Property Name
Get => locationName
Set => locationName = Value ' sorry about that capital V, @AnthonyDGreen
End Property
' These are reiterations of what @AdamSpeight2008 is proposing
' (my understanding of it, anyway)
Private fooeyText As String
Public WriteOnly Property FooeyNess As String => fooeyText = Value
Public ReadOnly Property Fooey As String => $"I say 'Fooey!' to that silly {fooeyText}."
' This is a reiteration of what @edin is proposing (my understanding of it,
' anyway), where the expression must be an 'l-value' (something assignable,
' such as a field or property) - hence must be read-able and write-able.
Private m_DataRow As DataRow
Public Property Id As Id => DataRow("col_ID")
Public Property FirstName As String => DataRow("col_FirstName")
Public Property LastName As String => DataRow("col_LastName")
Private Property DataRow(Of T) (name as String) As T
Get => CType(m_DataRow(name), T)
Set => m_DataRow(name) = Value
End Property
End Class
'==== Finalizers
Imports System
Public Class Destroyer
Public Overrides Function ToString() As String => GetType().Name
' This one may be tricky - include auto-generated call to MyBase.Finalize()?
Protected Overrides Sub Finalize() => Console.WriteLine($"The {ToString()} destructor is executing.")
End Class
'==== Indexers
Imports System
Imports System.Collections.Generic
Public Class Sports
Dim types As String() = { "Baseball", "Basketball", "Football",
"Hockey", "Soccer", "Tennis",
"Volleyball" }
Default Public Property Item(i As Integer) As String
Get => types(i)
Set => types(i) = Value
End Property
End Class
I just thought of this, the following:
'==== Indexers
Imports System
Imports System.Collections.Generic
Public Class Sports
Dim types As String() = { "Baseball", "Basketball", "Football",
"Hockey", "Soccer", "Tennis",
"Volleyball" }
Default Public Property Item(i As Integer) As String
Get => types(i)
Set => types(i) = Value
End Property
End Class
Could be done like this (idea from @edin):
'==== Indexers
Imports System
Imports System.Collections.Generic
Public Class Sports
Dim types As String() = { "Baseball", "Basketball", "Football",
"Hockey", "Soccer", "Tennis",
"Volleyball" }
Default Public Property Item(i As Integer) As String => types(i)
End Class
How about a keyword like Returns
or Gets
? It would match up with Handles
and Implements
, which also appear in that position.
Function Abs(i As Integer) As Integer Returns If(i < 0, -i, i)
ReadOnly Property Foo As Double Gets _foo
Property FooString As String Gets CStr(_foo) Sets _foo = Val(value)
@gilfusion How about a keyword like Returns or Gets? It would match up with Handles and Implements, which also appear in that position.
- [plus] Good in that it is more BASIC-like
- But I don't think it delineates (as clearly as a symbol like
=
or=>
) where the method body (expression) begins. It looks likeReturns If(...
is just additional keywords tacked onto the end of the property.- Also introduces complexity with the 3 different keywords:
Returns
,Gets
,Sets
.- And it is not clear what to do for a Sub.
Does
?
I see that a lot of people (@edin, @rskar-git) have expressed interest in doing this for Property Get/Set in one line -- i.e. Property Foo As String => items("foo")
creates both a getter with the return expression items("foo")
AND a setter items("foo") = value
.
While I appreciate the utility of the idea for concisely defining what could be called a "mapping," I must say that
items("foo")
has to be parsed twice, firstly as an expression, and secondly as the name of something assignable (variable name, field or writeable property). Also, the AST will have two nodes that point to the same tokens in the code -- not sure if this creates additional complexity. @AnthonyDGreen? Maps To
:
Default Public Property Item(i As Integer) As String Maps To types(i)
Either way, it is critical that this not be the only way to do expression-bodied properties; we have to allow for Get/Set separately as well, and also for ReadOnly Property
s in one line.
@bandleader @AdamSpeight2008 gave a thumbs-up to the post @edlin made on Apr 22, 2017, which elaborates on this "mapped properties" concept. All I did was paraphrase that, and then attempted to make some other hypothetical syntax (using C# examples as a go-by). I'm just trying to follow along. In the meantime, I believe AdamSpeight2008 is working on the prototype, and I don't know if he's looking into "mapped properties" or not.
You may be right that something like:
Public Property Item(i As Integer) As String => types(i)
may be something rather complex to implement, but wow! it's so handy it would certainly be worth trying.
@rskar-git I'm not currently working a prototype for this, wanting try and finish up some of the others first.
OK, so in summary for the VB team to review: (@KathleenDollard et al)
(Did my best to capture the ideas above: the various member types, our preference toward =>
, and the idea of mapped properties)
'=== Subs/Functions:
Function AddTwo(i As Integer) As Integer => i + 2
Sub PrintInt(i As Integer) => Debug.Print(i)
'=== Property get/set:
Property MyProp(i As Integer) As Integer
Get => i + 2
Set => Debug.Print("Done; thank you!")
End Property
'=== Read-only property -- only one line necessary:
ReadOnly Property MyProp(i As Integer) As Integer => i + 2
Function AddTwo(i As Integer) => i + 2 'no need to specify return type
However, many would say that any public member should specify its return type.Property Item(key As String) As String => _Params(key)
This would require the expression body to be parsed both as an expression and as an assignment target. More complex.Comments/feedback welcome from both community and VB team.
VB has many strengths vs the C-esque languages, a key one is that it doesn't aim for terse for the sake of being terse. I like this because terseness doesn't help the maintainer of the code who comes along in two years time and has to de-terse the code before they can debug it.
As part of that, I don't see any particular need to seek 'feature parity' with C# for its own sake, especially when it's for things that don't radically improve the language.
For example, I've never been a fan of anonymous methods, because when I'm writing code, I'm always thinking about the fact that I'm going to have to debug and tune it, and like to be able to add instrumentation code to methods, and to be able to see all my methods in a stack trace. And about 1/2 the time I've used anonymous methods, I've ended up refactoring them into actual methods anyway.
So while I get the goals in this thread, adding anonymous property getters using what looks like a C# lambda construct just feels wrong.
I realize that 'legibility' is subjective, but it's also contextual - what I find legible in C# code, I don't necessarily find legible in VB, and vice-versa. E.g. in this case, I don't find this:
Public Overrides ReadOnly Property Value As Integer => Throw New NotImplementedException()
more legible than the 'traditional'
Public Overrides ReadOnly Property Value As Integer
Get
Throw New NotImplementedException()
End Get
End Property
In fact, I find it quite the opposite. Possibly just because the latter is a pattern I've been looking at for the last 15 years, but I still think it presents better. And as someone has mentioned somewhere on this forum, would be easier for a non-programmer looking over a shoulder to understand.
I think that declarative stuff, like the @AnthonyGreen's stuff in #282 is ok, because it's about reducing repetitive boiler-plate code, and I can see how it will reduce errors, whereas I see these sneaky beggars as being more likely to increase them
@pricerc I can't see at all how you think the traditional way is more readable than the expression-bodied way. Nor how a non-programmer could understand it better.
Public Property x As Integer => someValue
is just like regular declarations:
Dim x As Integer = someValue
I can't understand why you feel multi-line boilerplate is desirable here -- when the entire body is just a single-line expression!
Function(c) c.Name
and factor it out into its own method GetCustomerName(c As Customer) As String
?Dim x As Integer = 5
:
Dim x As Integer
Set
x = 5
End Set
End Dim
ReadOnly Property ThisTime As Date = DateTime.Now
ReadOnly Property ThatTime As Date => DateTime.Now
Maybe this isn't an issue in languages where this syntax currently being used, but I can see there being some confusion between those two lines of code, which do quite different things.
@bandleader, as I said, style preferences are subjective, a personal preference.
So yes, surprising though it may be, I do prefer multi-line boiler plate for a method definition - I've been troubled by enough one-line anonymous methods whilst debugging, that I now avoid them.
And yes, I have refactored more than one single-line lambda into a discrete method - usually while debugging a performance problem, and in both C# and VB.
I'd also argue there is a subtle but important difference between 1) a property with a backing store and initial value and 2) a property with a backing method
The former I expect to happen once as part of class instantiation, and be optimized into the class constructor, whereas the latter is effectively a method declaration. For me (and I'm sure at least some others), the multi-line declaration is a pattern I recognise when scanning for methods, which are different from assignments.
My main concern is about maintenance. I don't know about you, but I sometimes have to maintain code that I haven't touched in several years, and that process often starts with scanning over the code; for me, being able to easily differentiate between methods and assignments is helpful in rebuilding an understanding of what it does. Currently, this is easy, as there is a clear distinction. But as mentioned by @gilfusion having to differentiate between these two would slow that process for me:
ReadOnly Property ThisTime As Date = DateTime.Now
ReadOnly Property ThatTime As Date => DateTime.Now
There is a suggestion in C# to use <=>
and I think it is more clear than =>
and gives the meaning of mapping:
Property ThisTime As Date = DateTime.Now ' initial value
Property ThatTime As Date <=> mydate ' map mydate to set and get
@AdamSpeight2008 @reduckted @AnthonyDGreen @scottdorman @xieguigang @KathleenDollard Another way:
Property ThatTime As Date Using mydate
I like this one. Using is reserved and gives a clearer meaning the the property is relying on the var or expression. It allows also to do this:
Property ThatTime As Date Using mydate = Date.Now
Which is equivalent to:
Dim mydate = Date.Now
Property ThatTime As Date
Get()
return mydate
End get
Set (Value as Date)
mydate = Value
End Set
End Property
This priposal is here for one yaer, and tey no decision made?
There is a suggestion in C# to use
<=>
and I think it is more clear than=>
and gives the meaning of mapping:Property ThatTime As Date <=> mydate ' map mydate to set and get
I don't understand how this could ever be useful.
If mydate
is private
and ThatTime
is just the public interface to mydate
, then you may as well just get rid of mydate
completely, and use ThatTime
.
The exception would be if you had an existing public mydate
, and you want to introduce a synonym for it called ThatTime
. In that case, I'd rather have a new keyword to deal with that, like Synonym <SynonymName> = <SourceName>
, than yet another symbol of dubious origin.
=>
, which I don't think is very 'VB', how about:
' instead of
Friend ReadOnly Property Kind As SyntaxKind => Return CType(Me.RawKind, SyntaxKind)
' a more VB style
Friend ReadOnly Property Kind As SyntaxKind = Get CType(Me.RawKind, SyntaxKind)
The Parser would process = Get
in the same way that @AdamSpeight2008 proposed it deal with => Return
. Except with less typing, and code that looks (to me) more like VB.
The overall goal as I interpret it is to enjoy a convenient (and highly readable) single-line syntax similar to C#. I admit it would seem a little odd to only have access to =>
specifically for expression-bodied members and not as a general shortcut syntax.
Would there be any ambiguity or limitation with just using Return
after the =
sign to denote an expression instead of an initialized value (certainly possible this is a redundant suggestion):
Public Readonly Property IsGrover as Boolean = Return CheckStuffOut()
The overall goal as I interpret it is to enjoy a convenient (and highly readable) single-line syntax similar to C#. I admit it would seem a little odd to only have access to
=>
specifically for expression-bodied members and not as a general shortcut syntax.Would there be any ambiguity or limitation with just using
Return
after the=
sign to denote an expression instead of an initialized value (certainly possible this is a redundant suggestion):
Public Readonly Property IsGrover as Boolean = Return CheckStuffOut()
I guess that's a subjective style choice. Either would work, but here's my thinking:
Function
with Iterator
) that I quite like the idea of, which (I think) is why I thought of having a Get
(or Set
, for that matter) as Property
appropriate.More completely, it goes a bit like:
Get
or Set
Return
Yield
Meow
Bark
(at strangers)Maybe if somebody wants to use the default property in order to avoid Dim
ing a private member but wants to add some check when setting, we can use a syntax like this:
' *partial*-default property
Public Property Content As List(Of String)
Get With Default
Set(Value As List(Of String))
If Value Is Nothing Then Throw New NullReferenceException()
Content = Value ' Access with property name
End Set
End Property
where getter equals to this:
Get
Return Content ' or equivalent inner private variable
End Get
We can even go further with setter(currently adds a kinda-frequently-used keyword Checks
, can be changed/use exist keywords):
Set(Value As List(Of String)) _
Checks Value IsNot Nothing Else Throw New NullReferenceException() _
With Default
Auto ReadOnly Properties are convenient for when we can express our intention at initialization
Sometime I like it to just as convenient to express what should happen at invocation, especially if it just a simple express / statement (IE a single Return / Throw ).
It would reduce the amount you'd have to read and type 80% per a "simple" property.