dotnet / vblang

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

An update from the Design Safari on the `INotifyPropertyChanged` scenario #282

Open AnthonyDGreen opened 6 years ago

AnthonyDGreen commented 6 years ago

Today

This latest iteration addresses the original scenario, is general, has a marketable semi-descriptive name imho, dodges the keyword naming debate, doesn't significantly complicate the compiler and potentially simplifies a lot of programs. It takes repetitive situations that force you to give up the declarative benefits of auto-props to write boilerplate setters and allows you to keep it declarative, which I believe is more in keeping with the spirit of the language.

I implemented the prototype, played with it, good stuff. Now I want to share and get feedback. Here's some screenshots that illustrate the value (I'll build a shareable prototype when I get to a real computer. I've implemented this on a wholly inadequate laptop that's painful to code on):

Here are the property declarations involved:

image

Here's what some instance handlers themselves look like: image

Here're some shared handlers (implementations in attributes themselves): image

Here's a doc comment helping a reader understand what an attribute does: image

Here's an example of the program running, showing off one of the validator attributes: image

The rest of the attributes I need to record interaction for you to see them work but...

Here's the generated IL for one of the setters:

image

And here's all the actual code for the various demos. Obviously most of this wouldn't ever appear in user-code: Models

Imports System
Imports System.Diagnostics
Imports System.Console
Imports System.ComponentModel

<AttributeUsage(AttributeTargets.Property, AllowMultiple:=False, Inherited:=True)>
Public MustInherit Class PropertyHandlerAttribute
    Inherits Attribute
End Class

#Region " Data-binding "
<Conditional("__NEVER__")>
Public Class NotifyAttribute
    Inherits PropertyHandlerAttribute
End Class
#End Region

#Region " Data hygene/transformation "
''' <summary>
''' Automatically trims leading and trailing whitespace from any value assigned to this property.
''' This trimming happens before subsequent handlers are executed.
''' </summary>
<Conditional("__NEVER__")>
Public Class TrimAttribute
    Inherits PropertyHandlerAttribute

    Public Shared Sub OnPropertySet(sender As Object, propertyName As String, ByRef backingField As String, ByRef value As String)
        value = If(value?.Trim(), "")
    End Sub
End Class

' This only effects compilation. Attribute never needs to appear in metadata.
<Conditional("__NEVER__")>
Public Class AutoRoundAttribute
    Inherits PropertyHandlerAttribute

    Sub New(digits As Integer)
        ' Because this constructor arguments are copied to the generated callsite,
        ' the 'digits' parameter doesn't actually have to be saved anywhere.
    End Sub

    Public Shared Sub OnPropertySet(sender As Object, propertyName As String, ByRef backingField As Decimal, ByRef value As Decimal, digits As Integer)
        value = Math.Round(value, digits)
    End Sub
End Class
#End Region

#Region " Validation "
<Conditional("__NEVER__")>
Public Class ThrowOnNullAttribute
    Inherits PropertyHandlerAttribute

    Public Shared Sub OnPropertySet(sender As Object, propertyName As String, backingField As Object, value As Object)
        If value Is Nothing Then Throw New ArgumentNullException(propertyName)
    End Sub
End Class

#Region " Soft validation "
<Conditional("__NEVER__")>
Public Class MaxLengthAttribute
    Inherits PropertyHandlerAttribute

    Sub New(value As Integer)

    End Sub
End Class

<Conditional("__NEVER__")>
Public Class RegexAttribute
    Inherits PropertyHandlerAttribute

    Sub New(pattern As String, message As String)

    End Sub
End Class
#End Region
#End Region

#Region " Out there "
<Conditional("__NEVER__")>
Public Class UndoableAttribute
    Inherits PropertyHandlerAttribute

    Public Shared Sub OnPropertySet(obj As Object, propertyName As String, previousValue As Object, ByRef newValue As Object)
        UndoRedo.Remember(Sub() CallByName(obj, propertyName, CallType.Set, previousValue))
    End Sub
End Class

Module UndoRedo
    ReadOnly UndoStack As New Stack(Of Action)
    ReadOnly RedoStack As New Stack(Of Action)

    Private IsUndoPending As Boolean
    Private IsRedoPending As Boolean

    ReadOnly Property CanUndo As Boolean
        Get
            Return UndoStack.Count > 0
        End Get
    End Property

    ReadOnly Property CanRedo As Boolean
        Get
            Return RedoStack.Count > 0
        End Get
    End Property

    Event StackStateChanged()

    Sub ForgetAll()
        UndoStack.Clear()
        RedoStack.Clear()
        RaiseEvent StackStateChanged()
    End Sub

    Sub Remember(action As Action)
        If Not IsUndoPending AndAlso Not IsRedoPending Then
            UndoStack.Push(action)
            RedoStack.Clear()
            RaiseEvent StackStateChanged()
        ElseIf IsUndoPending Then
            RedoStack.Push(action)
        ElseIf IsRedoPending Then
            UndoStack.Push(action)
        End If
    End Sub

    Sub Undo()
        Try
            IsUndoPending = True
            UndoStack.Pop().Invoke()
        Finally
            IsUndoPending = False
            RaiseEvent StackStateChanged()
        End Try
    End Sub

    Sub Redo()
        Try
            IsRedoPending = True
            RedoStack.Pop().Invoke()
        Finally
            IsRedoPending = False
            RaiseEvent StackStateChanged()
        End Try
    End Sub

End Module
#End Region

Class ListingInfo
    Implements INotifyPropertyChanged
    Implements IDataErrorInfo

    Private ReadOnly _Errors As New Dictionary(Of String, String) From {{NameOf(Title), ""}, {NameOf(Description), ""}}

    <Trim, Notify, Undoable, MaxLength(25)>
    Property Title As String = "New Listing"

    <ThrowOnNull, Notify, Undoable, MaxLength(50)>
    Property Description As String = "No description."

    <Notify>
    Property LastSaved As Date

    <Notify, Undoable, Regex("^\d{3}-\d{2}-\d{4}$", "Must be of the form '###-##-####'.")>
    Property ListingCode As String = "<None>"

    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged

#Disable Warning BC42353 ' Intentionally letting the function return 'False' by default.
    Protected Function NotifyOnPropertySet(propertyName As String, ByRef backingField As String, value As String) As Boolean
        If value = backingField Then Return True

        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Function

    Protected Function NotifyOnPropertySet(Of T)(propertyName As String, ByRef backingField As T, value As T) As Boolean
        If Object.Equals(value, backingField) Then Return True

        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Function

    Protected Sub MaxLengthOnPropertySet(propertyName As String,
                                         ByRef backingField As String,
                                         value As String,
                                         maxLength As Integer)
        If value?.Length > maxLength Then
            _Errors(propertyName) = $"Must be less than {maxLength} characters long."
        Else
            _Errors(propertyName) = ""
        End If
    End Sub

    Protected Sub RegexOnPropertySet(propertyName As String, ByRef backingField As String, value As String, pattern As String, message As String)
        If Text.RegularExpressions.Regex.IsMatch(value, pattern) Then
            _Errors(propertyName) = ""
        Else
            _Errors(propertyName) = message
        End If
    End Sub

    Private ReadOnly Property ValidationErrors(columnName As String) As String Implements IDataErrorInfo.Item
        Get
            Dim message As String = ""
            If _Errors.TryGetValue(columnName, message) Then
                Return message
            Else
                Return ""
            End If
        End Get
    End Property

    Private ReadOnly Property IDataErrorInfo_Error As String Implements IDataErrorInfo.Error
        Get
            Return ""
        End Get
    End Property
End Class

UI Code-Behind

Public Class Form1

    Private Listings As New ComponentModel.BindingList(Of ListingInfo) From {New ListingInfo}

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ListingInfoBindingSource.DataSource = Listings
        AddHandler UndoRedo.StackStateChanged, Sub()
                                                   UndoToolStripButton.Enabled = UndoRedo.CanUndo
                                                   RedoToolStripButton.Enabled = UndoRedo.CanRedo
                                               End Sub
        UndoRedo.ForgetAll()
    End Sub

    Private Sub ListingInfoBindingNavigatorSaveItem_Click(sender As Object, e As EventArgs) Handles ListingInfoBindingNavigatorSaveItem.Click
        Dim newLastSavedDate = Date.Now
        For Each item In Listings
            item.LastSaved = newLastSavedDate
            newLastSavedDate = newLastSavedDate.AddMinutes(1)
        Next
    End Sub

    Private Sub UndoToolStripButton_Click(sender As Object, e As EventArgs) Handles UndoToolStripButton.Click
        UndoRedo.Undo()
    End Sub

    Private Sub RedoToolStripButton_Click(sender As Object, e As EventArgs) Handles RedoToolStripButton.Click
        UndoRedo.Redo()
    End Sub
End Class

And here's my prototype branch.

Now I'm off to the Baltimore airport for another riveting day of flying across the country. Feedback welcomed!

-ADG

bandleader commented 6 years ago

Wow, @AnthonyDGreen, your idea is amazing!

The beauty of it, in my opinion, is that:

  1. at the compiler level, the feature is completely flexible and unopinionated, and relatively easy to implement,
  2. similarly, the feature is not tied specifically to INotifyPropertyChanged, and can be used completely independently, using custom handler methods on the type itself,
  3. and yet, handler methods can also be put on the attribute, sparing the user from writing custom handler code, as in TrimAttribute and AutoRoundAttribute in your example
  4. and, most importantly, we can make attributes that are tied specifically to INotifyPropertyChanged (or anything else), and spare the user from writing his/her own handler.

IMHO this last point is the strongest, and I think not clear enough in your description -- in your screenshots you handled INotifyPropertyChanged on the type itself, instead of on NotifyAttribute.

Amazing work!

A few suggestions

  1. Word breakup -- To make things more understandable, I think that instead of NotifyOnPropertySet, we should use Notify_OnPropertySet, to make clear that these are being called by the NotifyAttribute (and the notation is similar to Text1_TextChanged). Otherwise it reads like a string of words, as if Notify was a verb (and MaxLengthOnPropertySet reads completely senseless)...
  2. Property-specific handler methods -- For cleaner code, the compiler should also allow (on the type) handler methods that are specific to a certain property. Instead of a single Notify_OnPropertySet(propName, backingField, value) in which I have to check propName myself, I would sometimes prefer to handle things in a specific Notify_OnPropertySet_PropName method. (This would also slightly increase runtime performance, and would also remove the need for generics in this case.)
  3. Naming -- You spoke of marketable feature names -- what do you propose to call this feature? "Notifying properties?" And the flagship attribute would be NotifyAttribute, which has no handler of its own, and expects it to be on the type? And then we would ship a AutoPropertyChangedAttribute as well that does the work for you in the case if handling INotifyPropertyChanged?
  4. Usage with explicit properties -- Is there some way to leverage this feature even for explicitly implemented properties (rather than just for auto properties)? Couldn't the compiler simply call the appropriate handlers before calling the property's setter? (More accurately -- inject calls to the handlers inside the existing setter. Sort of how we inject a class's field initializations into its constructor -- I think).
  5. Backwards/lateral compatibility -- Since calls to the handlers would be inside the property's setter (at least on the MSIL level), I assume there is no problem in consuming the property setter from an earlier version of VB, or even C# or F# -- the handlers would still get called -- correct? (i.e. we aren't changing the IL required to consume the property -- only the generated IL inside the setter -- right?)
reduckted commented 6 years ago

@AnthonyDGreen My first impression is that it sounds like a good solution. I'll have to re-read it a few times to let it all soak in, but it's pretty impressive! 😄

@bandleader in your screenshots you handled INotifyPropertyChanged on the type itself, instead of on NotifyAttribute.

That's because it has to be handled on the type and cannot be handled in the attribute. The handler method needs to raise an event, and that can only be done from within the object that "owns" the event (I'm not sure if that's the right terminology). See @AnthonyDGreen's last bullet point before the "Today" section:

The problem is the attribute isn't in the same class as the property and can't raise any events in another object.

@bandleader 1. Word breakup

I have an aversion to underscores in method names, so I prefer the original naming proposal. 😄

@bandleader 2. Property-specific handler methods

I'm just thinking out loud on this one. I can see how this would easily slot into the proposed resolution logic, but is there a benefit to having a property-specific handler, as opposed to just not using an auto-property and putting that handler's logic in the setter? I guess by having the property-specific handler for one particular attribute, you could still specify other attributes and they would be handled using the normal logic. So yeah, I guess this makes sense.

@bandleader 4. Usage with explicit properties

I feel like this would open up a can of worms. If you let it work with "manual" properties, then it's a small step to want it in non-properties as well (subs, functions, custom constructors, event handlers, etc, etc). Maybe that's a good thing, but it feels like feature-creep. The idea works well for auto-properties because the compiler is already generating the body of the methods. This is just telling the compiler how to implement them.

@bandleader 5. Backwards/lateral compatibility

As far as I understand, yes, your assumption that you could consume them from earlier versions is correct. The compiler is simply generating different IL - you could implement the exact same logic yourself by manually calling the handler methods inside a "manual" property.

@AnthonyDGreen one thought that's popped into my head is when you have multiple attributes on a property, what determines the order that the handlers are called in? For example, you've got a TrimAttribute and a MaxLength attribute. Ideally you'd want the Trim attribute to be run first, so that whitespace is removed, allowing it to pass successfully through the MaxLength attribute (assuming the whitespace made the string too long). Are you simply using the order that the attributes are defined in? I could see that causing some unintended behaviour if things like code formatters are reordering attributes into alphabetical order.

vbcodec commented 6 years ago

There are much simpler ways to handle AOP like scenarios with INotifyPropertyChanged, and there is no need for heavy language's changes like https://github.com/dotnet/csharplang/issues/107 or https://github.com/dotnet/vblang/issues/194 or anything like that.. The point is that inheritance and polymorphism already allow for such scenarios, but there is missing piece to fully exploit it.

    Public Class SomeObj
        Public Overridable Property MyProp As Integer
    End Class

    Public Class SomeObjINPC
        Inherits SomeObj
        Implements INotifyPropertyChanged

        Public Overrides Property MyProp As Integer
            Get
                Return MyBase.MyProp
            End Get
            Set(value As Integer)
                MyBase.MyProp = value
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("MyProp"))
            End Set
        End Property

        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    End Class

The problem is that SomeObjINPC must be instantiated everywhere in place of SomeObj, and currently this require changes in code that create SomeObj instances, which is very uncomfortable. To solve it, there must be method (similar to extension methods), that overrides process of creating instances.

    Public Function New() As SomeObj
        Return New SomeObjINPC
    End Function

So, compiler must detect this method, and use it to create instance of returned type.

Dim o = New SomeObj ' Call New function that create SomeObjINPC

Such New generator may also be used for other purposes

KathleenDollard commented 6 years ago

@AnthonyDGreen if you agree with @reduckted 's point about order (the order listed) and you don't see implementation issues with that, perhaps include in proposal.

I am also worried about ordering of the in-class code. I may not want to notify if validation fails.

I'm somewhat concerned about returning a boolean. This slants the feature heavily to validation scenarios.

I think there are variations that alleviate these concerns.

I think this is cool and might be an interesting direction.

It feels like a foundation to a more complete set of work. It looks most interesting at first review as a basis for community implementation(s) of a set of meaningful attributes. This keeps individual developers from having to write their own implementations while leaving the implementation very flexible and dynamic.

I don't know how serious versioning problems would be in such a scenario.

AnthonyDGreen commented 6 years ago

@KathleenDollard totally agreed about it being a basis for community implementations. VB thrives when community members can share abstractions with each other to be more productive.

I wanted to try a mix of approaches out so a handler may return a Boolean or it could just be a Sub. I also wanted to try exception throwing validation ThrowOnNull and IDataErrorInfo-style validation, where the user can set bad state and the UI just reports it.

@reduckted yes, the ordering in source is significant. In fact if you can see in my examples I had to put the Notify attribute before most other attributes since that's where I put the code to check for duplicate values. I could have factored that logic into its own handler, I just didn't. But in the case of Trim and ThrowOnNull I put those before Notify because one transforms the value and the other will just throw an exception.

@bandleader @reduckted speaks the True True, in .NET it's impossible to raise an event from outside of the class that defined it. Not even a derived class can directly raise an event declared on its base class. That's why base classes will usually expose some protected method On<EventNameHere> that raises the event for derived types. So the only way to make a single attribute to handle INPC would be to ship the attribute with a base class that users could inherit that exposes such a method. It can't even be done with extension methods. In general though, I think every system I've ever written has resulted in me defining my own base classes for common behaviors like this so it's not a loss if the cost of two-way binding goes from once per property to once per program.

KlausLoeffelmann commented 6 years ago

Hey Anthony,

My thoughts:

There are VB enthusiasts like us, who would love this feature.

Those enthusiasts would like VB to be as (good/grown up/flexible/Professional/enter your adjective here) as C#. Basically, they are good C# developers, but like the VB Language “taste” better, so they use VB.

And then there is the typical VB developer. He might use attributes, but I’m not sure if he writes them. He might use inheritance, but only “as directed”.

This feature is for the C# VB developer, I think.

It leaves out what feels like 80% of the TYPICAL VB audience, who do not know about when to use more than implicit inheritance (when creating a new WinForms form, e.g) or when to write custom attributes. And they should not need to know.

So, as a voice for them I have to say: this feature is not for VB. It is WAY too complicated to use.

I, however, would love it.

Klaus

KathleenDollard commented 6 years ago

@KlausLoeffelmann

If there was a VBContrib library, do you think it could have attributes available for use that the typical VB developer could embrace?

That is where I see the potential value of this feature.

franzalex commented 6 years ago

I totally agree with @KlausLoeffelmann. The typical VB.NET developer may not know (or care) about such a feature. It's those advanced VB.NET developers that are likely to implement and use this.

Being in the advanced group, this concept is very appealing but I can't help but wonder if beginner and lower intermediate level developers will have the same level of interest.

franzalex commented 6 years ago

@AnthonyDGreen

Would it be possible to include a 'precedence' property in the attributes? That way the compiler/parser can determine the order of execution of the associated code. If not then the method associated with a custom attribute could end up being called at the wrong time.

KathleenDollard commented 6 years ago

@AnthonyDGreen

I am concerned about the backing field. Take the case in #279

You don't use the backing field, but you'd still like to use validation features and mutating features like your Trim and AutoRound.

I'm imagining a VBContrib style library that had people inherit from a ViewStateWrapper class and add attributes. How would they know that these attributes didn't work, or is there a way to make them work?

Kathleen

pricerc commented 6 years ago

@KlausLoeffelmann - I think you insult VB.Net developers by suggesting that the good ones are actually just good C# developers looking for variety. I would argue that the best C# developers are actually just good VB developers investigating what all the fuss is about... :)

Getting back on topic... I think this exactly the kind of thing that might encourage some of those 'typical' VB developers you refer to, to expand their horizons into the wonderful world of attributes, especially if there are handy libraries of helpers to ease their way into modern coding patterns, which they could then also more readily embrace (I know the pain of INotifyPropertyChanged has been enough to make me pack up my bags and go home early so that I could psyche myself up for a day of copy, paste and regex-replace....)

bandleader commented 6 years ago

@KlausLoeffelmann So, as a voice for them I have to say: this feature is not for VB. It is WAY too complicated to use.

Respectfully disagree. I'm also an advanced VB developer who also uses C#, but even if you're talking about beginners: where they don't understand something 100%, they are fine with copying from StackOverflow and adapting it to their needs. Remember that back in VB6 people were doing amazing things by copying some Win32 API code from PlanetSourceCode or CodeProject!

But secondly, I don't think that VB is a beginner's language. It was never meant to limit developers, but to give them all the power possible as cleanly, beautifully and approachably as possible -- letting them not just start out programming but become master programmers in a syntax that is beautiful to masters, beginners, and laymen alike. That's why MS earlier had VB.NET on feature parity with C#; there's nothing about the language that throws advanced users away -- nor should we have any.

Remember that VB6's popularity was the ability to make (in a fraction of the time and complexity) all the "regular" Windows desktop apps that were popular at the time. Today, people aren't making Windows desktop apps; they're making mobile apps (and some cross-platform and web apps) with MVC/MVVM frameworks. (An imperative WinForms app is simply not competitive today.) Inasmuch as INotifyPropertyChanged is necessary for that, this feature doesn't complicate things; on the contrary, it simplifies implementation of an inteface that is a basic necessity for giving users to power to make today's apps.

bandleader commented 6 years ago

@KathleenDollard If there was a VBContrib library, do you think it could have attributes available for use that the typical VB developer could embrace?

I imagine the attributes would go directly into the BCL, because I am sure that other languages (like C#) will eventually want to take advantage as well. This isn't a VB-specific thing; it's a common need to have data classes with reduced boilerplate -- Scala for instance calls them "case classes."

@KathleenDollard I am concerned about the backing field. [...] You don't use the backing field, but you'd still like to use validation features and mutating features like your Trim and AutoRound.

In @AnthonyDGreen's design, the compiler passes the newly-being-assigned value to each attribute as ByRef value and each attribute could validate and even mutate the value if it desires. They do not have to use the backing field directly. (In fact, he had mentioned that perhaps the backing field could even be omitted when not necessary i.e. when there's an alternate store -- see his last paragraph here.)

@franzalex Would it be possible to include a 'precedence' property in the attributes?

Why wouldn't we simply do it in the order the attributes were declared?

@AnthonyDGreen In .NET it's impossible to raise an event from outside of the class that defined it.

I propose that we need to find a way around this, whether that means cooperating with the CLR team, or simply using one of the 'hacks' available on the internet -- I did a quick search and found some interesting results. The reason is because with all the talk about supporting first-day-programming beginners, it is indeed hard to expect them to figure out on their own how to create a method with the right name and signature and raise PropertyChanged -- they would want to just declare the NotifyPropertyChanged attribute and be done with it.

reduckted commented 6 years ago

@bandleader Why wouldn't we simply do it in the order the attributes were declared?

See my concerns above about code formatters reordering the attributes.

KlausLoeffelmann commented 6 years ago

@KathleenDollard : The only way I see this feature would make sense in the original spirit of VB was, if we considered @AnthonyDGreen 's concept to be rather infrastructure, and we put ready to use base classes and/or attributes in the My-namespace. VbContrib lacks discoverability for too many of the VB devs. Since those should be available for .NET Core/.NET Standard, maybe we could have new templates for VS/CLI which then include a VB specific NuGet which would contain an extended My-Namspace for each respective project type.

We need to talk about updating My-Namespace anyway. The infrastructure of the compiler - just in case folks did not know that - already allows that without modifying compiler or framework libs. This would be another great community initiative, btw!

@bandleader : I wouldn't call VB at all an exclusive beginner language, because it is not. Vb is a compromise Productivity vs. Flexibility. I'm just talking about approachabiliy and discoverability. We need new functionality to be picked up by the whole audience instantly. If the majority does not get it on first glance, they cold-shoulder it most probably. We need something which I'd call Guidance by Product. Things must be so intuitive that VB folks pick them up just like that.

Things that made VB6 great were

And later in .NET

Functionality in that spirit can be the only unique selling feature for VB. Parity for everything to C# doesn't make and never made any sense. If I wanted everything like in C#, why wouldn't I take C#: Which is exactly what MILLIONS of devs did. VB had an identity crisis for a long time. We need to find a way to give it back it's own identity, it original self. Otherwise, VB will end up as a boring Zomby, which will only get those things which are needed to consume necessary platform improvements. Like Interface methods.

Klaus

reduckted commented 6 years ago

And then there is the typical VB developer. He might use attributes, but I’m not sure if he writes them. He might use inheritance, but only “as directed”.

I don't understand this mentality. We've got a good feature (yes, it could be improved and refined, but it's still good), but just because some developers who use the language wouldn't feel comfortable using it, we must throw it away? Why?

So, as a voice for them I have to say: this feature is not for VB. It is WAY too complicated to use.

No one is forcing them to use it. For those of us who know how to use VB to it's full extent, this is a great idea, and we shouldn't be hamstrung by novice developers for fear of them not understanding it. Documentation and tutorials can help them.

This sort of thinking is, in my opinion, what feeds the hatred and ridicule of VB.NET. The idea that the language is a "beginner language" and therefore must not contain advanced concepts or anything that's too complicated. This feature would be really useful for things like view models in an MVVM-based WPF application. If this "typical VB developer" is creating an MVVM-based WPF application, then surely they can understand this proposed concept.

Maybe I'm just really out-of-step with what a "typical VB developer" is, given that I, and my colleagues at work, are all highly competent VB.NET developers.

KlausLoeffelmann commented 6 years ago

@reduckted: Couple of questions:

I am curious about what you and other people think!

Bill-McC commented 6 years ago

First of, I really love this idea. I like it has a hint of extensibility. To me that's a future safeguard, an avenue to deal with change and evolve without having to wait for a new version of VB with yet more keywords to deal with new frameworks or patterns.

I would like to see more detail on how the compiler knows what to inline.

Re typical VB developers... stuff 'em ;) Yes I said that. If they can't speak up for themselves I wonder if they should be programming in the next era. I think the biggest problems VB faces are the lack of community involvement; this notion that we can't have anything extensible because we need defined sandboxes and prescribed buckets and shovels. I don't believe the "average" VB developer doesn't use attributes, rather I believe they use them when they have to, designers, serialization, data contracts, etc. But more importantly I think we need to stop projecting this notion of a VB developer as "hopeless". We need to give them hope, and this feature to me ticks all those boxes. I strongly believe VB needs to grow up and get ready to move out of home.

bandleader commented 6 years ago

Completely with @reduckted and @Bill-McC on this one. Yes, Visual Basic is popular among beginners for various reasons, but as I said, why can't they continue to use it even once they're advanced programmers? Isn't that its whole value? The isn't MS's toy language. The goal here is to make it approachable for beginners to get started -- not to omit any feature that beginners will not be able to immediately use and understand.

"VB is as powerful as other languages, but much cleaner, and with much more sugar and elegance." -- A Wise Man

@KlausLoeffelmann Visual Basic is de facto an entry level language. The telemetry proves that. It's fact.

"An entry-level language?" It doesn't prove that the language is unsuited to advanced developers, just that advanced VB developers are frustrated at MS's abandonment of it, and have left for C#.

@KlausLoeffelmann Do you think a feature like that would help to move a C# team back to VB?

This isn't about moving C# teams to VB, but about making each language as great as it can be. We don't need feature differentiators per se; it's a syntax preference. Simple feature parity would go a long way towards making VB viable.

@KlausLoeffelmann If VBs popularity can be improved with features which are for most of the target audience, should we not concentrate of those features, because we only have so much time or money?

That's not what's happening. MS is not improving VB and focusing on beginner-friendly features. They're improving C# and letting VB fall by the wayside.

@reduckted No one is forcing them to use it. For those of us who know how to use VB to it's full extent, this is a great idea, and we shouldn't be hamstrung by novice developers for fear of them not understanding it. Documentation and tutorials can help them. / This sort of thinking is, in my opinion, what feeds the hatred and ridicule of VB.NET. The idea that the language is a "beginner language" and therefore must not contain advanced concepts or anything that's too complicated.

Gold. See also what I wrote above about the amazing ability of beginning programmers' to copy code from StackOverflow (and actually improve their programming skills in the process).

reduckted commented 6 years ago

@KlausLoeffelmann Why do you think, I proposed to throw away this features as you wrote?

The impression I got from your comments was that it was too complicated: "this feature is not for VB. It is WAY too complicated to use."

You wrote some developers. I'm interested: What would be your estimation, how many (typical) VB developers would be overwhelmed with @AnthonyDGreen's feature as it is.

I wrote "some" because that's what you said. You said "It leaves out what feels like 80% of the TYPICAL VB audience,". There's your percentage that you wanted. When I wrote "some", I was referring to the 80% that you came up with.

KlausLoeffelmann commented 6 years ago

Again: what do you think is the percentage? I am just trying to get a feeling if my estimation is shared by others.

pricerc commented 6 years ago

Even if you think this proposal is only for 'advanced' coders, I don't see how you can extrapolate that perspective into "this proposal doesn't belong in VB, because it's too advanced for VB coders"

Besides the insult that is to advanced VB developers everywhere, VB has always been a tool for developers who want the improved productivity provided by VB, and the more skilled among them have used other (COM/ActiveX) languages or hooked into Win32 to get things done that are not natively available. So VB has always catered for 'entry level' and advanced users.

So I'm puzzled by this argument that this proposal is too sophisticated for VB, because it may spook inexperienced or 'Typical VB' coders.

If that's your view of VB, then what's the point of this forum? One could argue that VB already has all the features the 'typical' or 'inexperienced' coder, and if that was the target market for VB.Net, then the job is complete, except for bug fixing and performance improvements.

But that's at best a naive view of the language, because you believe that VB is an 'entry level language', and that VB coders are by definition less skilled than, say, C# coders. At worst it's a malicious view of the language because you have some agenda to advance that involves the demise of future development of the language.

Nukepayload2 commented 6 years ago

@KlausLoeffelmann I don't think this feature is too complicated for most of the VB programmers to understand. We already have many features that depend on custom attributes. Such as extension methods, hide module name, PInvoke, COM interface import/export and thread fields. If this feature accompany with nice intellisense behaviors for build-in auto-implemented property handler attributes, they will get used to it swiftly. For example, if user type the following code:

<Notify>
Public Property Count As Integer

And press enter, or press the > key at the following position:

<Notify Press ">" key here
Public Property Count As Integer

If NotifyOnPropertySet is not exist nether in this class nor in base classes, the following code will be inserted automatically:

#Region "Auto-implemented property handlers"
' Visual Basic will handle auto-implemented properties' Set part with this Function if they have NotifyAttribute.
' Usage:
' <Notify>
' Public Property <PropertyName> As <PropertyType>
' For more information, please visit (doc link).
'''<Summary>
''' Raise PropertyChanged event when the property is being set to a different value.
''' </summary>
Protected Function NotifyOnPropertySet(Of T)(propertyName As String, ByRef backingField As T, value As T) As Boolean
    If Object.Equals(value, backingField) Then Return True

    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Function
#End Region
AnthonyDGreen commented 6 years ago

Whoa, didn't see this conversation had heated up (was too busy packing).

I think we're all mostly in what the language design meeting calls "violent agreement"; it looks like we disagree more than we actually do. @KlausLoeffelmann this is exactly as you describe it--infrastructure. I've describe a mechanism that could be implemented in the compiler to make it a little more productive to do certain kinds of things. I've also fleshed out the first draft of that design by trying to build a dozen example things. But that thing that would be "shipped" or advertised in a future version of VB would be those things. This mechanism would be a footnote, declarative MVVM would be the headline. In the way that My.Forms and My.Settings is the headline in VB2005 but the MyCollection extensibility stuff is some infrastructure stuff that goes in the spec. Maybe at the bottom of some list on docs.microsoft.com for the curious. I also agree that the NotifyAttribute and a base-class implementing the interface, declaring the event, and overridable methods for the property handler and OnPropertyChanged method in particular should go into the Microsoft.VisualBasic namespace or some other namespace imported by default for new VB projects. I repeat: NO declaring of attributes, or base classes, or events should be required for first-time programmers.

Yes, many intermediate or advanced VB developers will eschew the pre-canned base class for their own base class but... so what. The goal is for first-time developer to be crazy productive out of the box.

Adding a new ViewModel to a VB project should be as straight-forward as adding a new Form.

So for just that top echelon of VB developers. The P/Invokers and compiler extenders and framework authors, I ask:

AnthonyDGreen commented 6 years ago

@bandleader

It can't really be worked around with the CLR team. To the CLR there are no "events" really. There are just (usually) public methods named add_X and remove_X. It just so happens that the compiler generates a private field of delegate type and it generates those methods with code to mutate that field (and for VB only a private raise_X method). But there's no guarantee that field exists or that those methods do anything or any way for any compiler in any language to know that that private field exists or how to access it in order to raise it. That's why implementers have always had to manually declare protected OnX methods to raise it even in derived types. And there's definitely nothing in the interface of INotifyPropertyChanged to let a piece of code in one assembly just raise any class's PropertyChanged event and we could never add it because that would be a breaking change. It's really quite intractable :(

VBAndCs commented 4 years ago

Please consider also a Constructed Attribute as I proposed here: https://github.com/dotnet/roslyn/issues/38367

bandleader commented 4 years ago

@VBAndCs I like your Constructed Attribute proposal and responded, but could you explain its connection with this proposal of Anthony's?

VBAndCs commented 4 years ago

@bandleader Both uses attributes to modify the property. In fact I replied here as a failsafe, in case of Roslyn team says it is a language related feature, or are not interested to add it to C# as well so it can be a VB feature only. If VB will upgrade property definition, so this is the place to refer to my proposal to be considered. Thanks.