Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
antarr opened this issue Mar 7, 2018 · 9 comments

Comments

@antarr
Copy link

antarr commented Mar 7, 2018

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
Copy link
Contributor

How do you anticipate declaring the ViewStateProperty?

@antarr
Copy link
Author

antarr commented Mar 8, 2018

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

@antarr
Copy link
Author

antarr commented Mar 11, 2018

@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
Copy link
Contributor

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
Copy link
Contributor

@antarr please check this out: #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
Copy link

bandleader commented Mar 14, 2018

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
Copy link
Contributor

@AnthonyDGreen

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

@AnthonyDGreen
Copy link
Contributor

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
Copy link
Contributor

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

I want to move this conversation into #282

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants