This project is read-only.

[Possible Submission] - Allow the subject of a With Block to be referenced from within the block

Topics: VB Language Design
Apr 12, 2014 at 10:15 AM
VB Team,

This is a feature request from a while back I submitted to UserVoice:

http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3434723-allow-the-subject-of-a-with-block-to-be-referenced

Now that Roslyn is open sourced (hooray!) I pulled down the solution and implemented the feature to get an idea of the scope and impact.

Here's an example and brief discussion:
With New Request
   .SetAProperty("Grover")
   PassAsParameter(WithObj)

   .InvokeAMethod()
   Return WithObj
End With
What I like about this is that there is no need to declare a variable name which in many cases is just noise. It completely captures the expression within the scope of the With Block which is clean and readable.

Contrast with:
Dim r = New Request
With r
   .Do_A_Bunch_Of_Stuff_That_Cant_Be_Done_Within_An_Initializer
End With
Return r 'unnecessary construct
I'm not married to "WithObj" but it seemed pretty straightforward when compared with some alternatives (such as WithRef, or .Me, or just . ) .Me seems to conflate member access with a self reference, while just . seemed confusing since it marks the beginning of a left-omitted member access expression. It's easy enough to change the name of the keyword, though.

The changes to the source include:
  • Adds to Syntax.xml and BoundNodes.xml
  • Adds to Syntax files to support WithObj as a keyword
  • Addition of ERR_WithObjOutsideOfWithBlock and diagnostic test
  • Add to Binder_Expressions.vb (captures the withstatement and its type)
  • Add to EmitExpression.vb (simply reflects back to EmitExpression, passing the withstatement)
Does something like this fit into the Product Roadmap? Happy to submit these changes and/or modify as per discussion. It would be awesome to have this small change added in.

Thank you,

Craig Johnson.
Apr 12, 2014 at 7:02 PM
What about this alternative syntax
With r = New Request
   .SetAProperty("Grover")
   PassAsParameter(r)
   .InvokeAMethod()
   Return WithObj
End With
which makes it similar to Using x As New Foo it then could reuse some the same code use for Using?
This then could expand to include Using With r = New Request()
Apr 12, 2014 at 8:22 PM
Adam, that would work great. I do like the flexibility of either an explicit or implicit variable declaration and would use both. Having said that, the explicit variable usage would likely have the most widespread appeal.

And yes, combining With and Using would be fantastic as well.

Scenario 1: With Block (variable)
With r = New Request
   .SetAProperty("Grover")
   PassAsParameter(r)
   .InvokeAMethod()
   Return r
End With
Scenario 2: With Block (implicit variable)
With New Request
   .SetAProperty("Grover")
   PassAsParameter(WithObj)
   .InvokeAMethod()
   Return WithObj
End With
Scenario 3: With+Using (variable)
With Using r = New DisposableRequest
   .SetAProperty("Grover")
   PassAsParameter(r)
   .InvokeAMethod()
   Return r
End With
Scenario 4: With+Using (implicit variable)
With Using New DisposableRequest
   .SetAProperty("Grover")
   PassAsParameter(WithObj)
   .InvokeAMethod()
   Return WithObj
End With
Not sure what makes more sense - to have With, then Using or Using, then With. I think I prefer With first since in my mind I'm saying "With this disposable resource, do this and this and this" instead of "Using this disposable resource and now with that resource do this and this and this." Using is sort of an attribute of the thing we're working with so I think With makes more sense to have first.

The two could always be combined:

Scenario 5: WithUsing (variable)
WithUsing r = New DisposableRequest
   .SetAProperty("Grover")
   PassAsParameter(r)
   .InvokeAMethod()
   Return r
End WithUsing
Overall, I think I prefer scenarios 1 and 3.
Apr 17, 2014 at 4:56 PM
Before I submitted this post I read the "How To Contribute" document, read the two OSS etiquette blog posts contained in it, and signed the Open Agreement. Since the Contribute document indicated that no pull requests would be considered without first having a discussion, I posted this. It's early in the process and so I'm curious to see how this will play out and to understand what constitutes the "extremely high bar" of submission required to contribute anything substantive besides things like grammatical tweaks to documentation.
May 1, 2014 at 8:54 PM
With the proposed syntax, what would be the effect of:
Dim Pt(4) As System.Drawing.Point
With Pt(myPoint = Pt(1))
  .X = 4;
  myPoint.Offset(0,5)
End With
I don't see any way of avoiding astonishment with the proposed syntax. If myPoint behaves as an alias to Pt(1), that will be unlike any other behavior associated with =. If it behaves as a copy of Pt(1), then its behavior wouldn't match that of the thing modified by .X.

The syntax I would like to see would be to simply have (.) refer to the thing upon which the With statement is operating. I don't think that would introduce any syntax ambiguities, and its failure to behave like a normal variable shouldn't cause astonishment because it wouldn't look like a normal variable.

Also, as a variation on the proposed ?. operator, it might be useful to have something like:
With (MyThing : .Member1 : .Member2 as Fnord : .Member3.Member4)
... code using Ctype(MyThing.Member1.Member2,Fnord).Member3.Member4
Else
... code to evaluate if the `With` statement couldn't be evaluated because myThing, .Member1, .Member2, was
... null, .Member2 wasn't a Fnord, or .Member3.Member4 was null (note that if Member3 is null, code will
... throw an exception).  Typecast logic may require slightly-icky code generation, but it shouldn't be too bad.
End If
May 2, 2014 at 4:20 AM
Edited May 2, 2014 at 4:22 AM
@supercat some thoughts on your reply:

I would definitely like and use (.) if available, as in:
     Dim pt(4) As System.Drawing.Point

     With pt(3)
         .X = 10
         .Y = 20
         DoSomethingWithAPoint(.)
         Console.WriteLine(.X)
         Console.WriteLine(.Y)
      End With
The code above works in my clone of the source except I am using WithObj for (.) as the keyword. I added the keyword to the XML syntax docs which autogen into some gnarly monster classes, followed by making changes to Binder_WithBlock (mirroring TryBindOmittedLeftForMemberAccess -> TryBindForWithObjAccess - this simply returns back the withblock's expression which I subsequently capture in a BoundWithObjReference node), then on the codegen side, added EmitExpression.EmitWithObjReference which simply emits back whatever expression was captured earlier). From what I could tell this is basically what happens at the beginning of any emit for the With block, only I'm not emitting anything else past the original reference.

I couldn't figure out on first glance how to disambiguate the (.) so I just created the WithObj keyword, though I mentioned at the beginning it's a somewhat arbitrary choice.

Would love to hear from the language team if they could add this (or at least look at the proposed changes to assess viability). If I could do it, they could do it 10 times better, and maybe even work in the (.) syntax to boot.

Craig.
May 2, 2014 at 9:04 PM
Edited May 2, 2014 at 9:05 PM
One workaround I've used for this scenario was to define an extension method that just returns its instance like this:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Runtime.CompilerServices
Imports System.Drawing

Module Program

    Sub Main(args As String())

        Dim pt(4) As Point

        With pt(3)
            .X = 10
            .Y = 20
            DoSomethingWithAPoint( .Self())
            Console.WriteLine( .X)
            Console.WriteLine( .Y)
        End With

        Console.WriteLine(pt(3))

    End Sub

    Private Sub DoSomethingWithAPoint(point As Point)
        Console.WriteLine(point)
    End Sub

    <Extension>
    Function Self(Of T)(obj As T) As T
        Return obj
    End Function

End Module
It's not quite as elegant as a new language feature might seem but it's waaay cheaper to design/implement/test.

Regards,

-ADG
May 2, 2014 at 9:52 PM
ADGreen wrote:
It's not quite as elegant as a new language feature might seem but it's waaay cheaper to design/implement/test.
There's a reason my example used the Offset method of System.Drawing.Point. If (.) were synonymous with the With variable, then (.).Offset(3,4) should work. By contrast, .Self.Offset(3,4) would yield broken code.
May 2, 2014 at 10:57 PM
@ADG For my pull request I would only charge you for billable hours at my normal rate ($n/hr) * my OSS scaling factor (0.00%) :)
May 4, 2014 at 1:21 PM
supercat, (.).Offset(3, 4) when you could just type .Offset(3, 4)

I think that the only scenario this feature should address is passing the With subject all by itself. Why is it you don't want to give this object a name by explicitly declaring the variable?

-ADG
May 5, 2014 at 12:04 AM
I shouldn't have used (.) with the .. operator directly (though I would think such a construct should be allowed). Sometimes I don't formulate perfect examples.

ADGreen wrote:
I think that the only scenario this feature should address is passing the With subject all by itself. Why is it you don't want to give this object a name by explicitly declaring the variable?
Given the construct:
Dim MyArrayOfPoints(1) As Point
MyArrayOfPoints(0) = New Point(1,0)
With myPt = MyArrayOfPoints(0)
  .X += 2
  Debug.Print("{0}", myPt.X)
  myPt.Offset(4,0)
End With
Debug.Print("{0}", MyArrayOfPoints(0).X)
What values should be printed?