dotnet / vblang

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

Overloading Getter And Setter of Property Using Attribute (FEATURE REQUEST) #279

Open antarr opened 6 years ago

antarr commented 6 years ago

I want to create a custom attribute that overrides the getter and setter of a property so that I can reuse some logic.

CASE

I want to create an attribute for properties that use the ViewState instead of a backing field. I can't do this without being able to override the getter and setter.

MY Code without the attribute

Public Property sQuestionaire As Object
    Get
        Return Me.ViewState(NameOf(sQuestionaire))
    End Get
    Set
        Me.ViewState(NameOf(sQuestionaire)) = Value
    End Set
End Property

New Code with the attribute

<ViewStateProperty>
Public Property sQuestionaire As Object 
KathleenDollard commented 6 years ago

How do you anticipate declaring the ViewStateProperty?

antarr commented 6 years ago

@KathleenDollard There was a problem with my code above. I've updated it.

antarr commented 6 years ago

@KathleenDollard This is just a rough attempt.

    Private Class ViewStatePropertyAttribute
        Inherits Attribute

        Public Overrides Function Getter(<CallerMemberName> Optional memberName As String = Nothing) As Object
            Return ViewState(NameOf(memberName))
        End Function
    End Class
AnthonyDGreen commented 6 years ago

Ha! @antarr, you're in super luck. I literally just got a prototype of a design for something like this working this very week. I'm typing up the issue right now. I'll make sure to include this scenario.

AnthonyDGreen commented 6 years ago

@antarr please check this out: https://github.com/dotnet/vblang/issues/282

It mostly is what you're asking for. It's missing an important piece for your scenario which is a way to opt out of backing field generation entirely for an auto-prop. I'd like to do this but didn't include it in my initial proposal.

bandleader commented 6 years ago

While I love @AnthonyDGreen's #282, I just wanted to point out that this scenario is also resolved (and actually, most elegantly resolved) using expression-bodied properties (#61) -- assuming we follow the proposal there to allow assignment as well.

Public Property sQuestionaire As Object => Me.ViewState(NameOf(sQuestionaire))
KathleenDollard commented 6 years ago

@AnthonyDGreen

I'm not yet seeing how #282 solves this. What would the syntax be?

AnthonyDGreen commented 6 years ago

Ah, I see. I guess I used weaker language there than I intended.

The syntax is the same. #282 proposes a way for an attribute to specify user-defined methods which are called by the compiler in either the Set of an auto-prop, or the Get of an auto-prop, or both. All of my examples only showed handling the Set because that's the more common scenario (and all I had implemented at the time). But it was always the intention to support Get handlers, specifically for scenarios like this. @antarr gave ViewState as his use case, which is perfect. But the scenario can easily extend to things like strongly-typed wrappers around JObject instances, ExpandoObject property bags, WMI classes, sparse object graphs, lazily loaded properties, resource classes, etc.

I've updated my prototype to support that scenario for the sake of playing with those ideas. Here's what it looks like (this example is straight out of the unit tests):

Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Console

MustInherit Class PropertyHandlerAttribute
    Inherits Attribute
End Class

Class WrapperAttribute
    Inherits PropertyHandlerAttribute
End Class

Class ListingInfo
    Public ReadOnly ViewState As IDictionary(Of String, Object) = New Dynamic.ExpandoObject()

    Protected Sub WrapperOnPropertyGet(Of T)(propertyName As String, ByRef backingField As T, ByRef value As T)
        value = ViewState(propertyName)
    End Sub

    Protected Function WrapperOnPropertySet(Of T)(propertyName As String, ByRef backingField As T, ByRef value As T) As Boolean
        ViewState(propertyName) = value

        ' Never store values in the backing field.
        Return True
    End Function

    <Wrapper>
    Property Rating As Integer = 3

    <Wrapper>
    Property Category As String = "General"

    <Wrapper>
    Property Subcategory As String = "<None>"

    Function BackingFieldsToString() As String
        Return _Rating & _Category & _Subcategory
    End Function

End Class

Module Program
    Sub Main()
        Dim listing = New ListingInfo
        Write(listing.BackingFieldsToString())
        Write(listing.Rating & listing.Category & listing.Subcategory)
        Write(listing.ViewState!Rating & listing.ViewState!Category & listing.ViewState!Subcategory)
    End Sub
End Module

In this example the handler is very tied to the class where the wrapper attribute is being used, but it's easy enough to break that out into a reusable base class, e.g. WrapperObject that defines the handler methods and then each of your ViewState wrapper types can just inherit it and tag the appropriate properties.

There's one missing ingredient from this approach, which may or may not matter to you. As it is today the auto-props will always declare a field though do to the nature of this handler they aren't used. For scenarios like this one could imagine some mechanism which tells the compiler to not bother with a backing field at all as an alternate store is being provided by the handler. I did not include that in the original issue as it's somewhat orthogonal to the core design of the property handlers feature. For some cases you might still want the backing fields for caching (e.g. <Lazy>) while for others you very much don't want the field to save memory (e.g. <Sparse>).

KathleenDollard commented 6 years ago

Got it. I didn't see how the backing field helped in this case.

I want to move this conversation into #282