MILO
MILO

Reputation: 295

What exactly are parentheses around an object reference doing in VB.NET?

I have some procedures that take Control object references as a parameter.

I have a bunch of Controls throughout my project of varying derived types such as Button, TextBox, PictureBox, ListBox, etc.

I was calling the procedure and passing the reference as normal:

Procedure(controlRef)

I changed some of the Warning Notifications in my project configuration. I'm guessing it was changing the Implicit Conversion Notification from 'None' to 'Warning' that caused warnings similar to the following to appear everywhere these procedures were called:

"Implicit conversion from 'Control' to 'Button' in copying the value of 'ByRef' parameter 'parControl' back to the matching argument."

This makes sense, I'm doing an Implicit Conversion, but hang on a second, I'm passing a Button in to a Control parameter, not a Control to a Button like it says, I'm slightly confused what's happening here.

Anyway, I take a look at the "Show potential fixes" and there is no fix suggestion, only Suppress or Configure options, okay. So I do a explicit cast using DirectCast(controlRef, Control) to see if that'll remove the warning on Implicit Conversion, which it does, but it gets replaced by a Redundant Cast warning, again, this makes sense. So I remove the cast using the potential fixes and the argument in the procedure call is left with parentheses around it and no more warnings.

Procedure((controlRef))

What is going on here exactly?

Upvotes: 0

Views: 217

Answers (2)

jmcilhinney
jmcilhinney

Reputation: 54417

This is not an answer to the question but it may be a solution to the actual problem. It also requires significant code, so I decided an answer was the best option.

One has to wonder why you have declared that parameter ByRef in the first place. Many people do so when it is not required because, as in VB6, they think that it will prevent an object being copied. That is not the case because reference type objects, i.e. class instances, don't get copied when passed by value anyway. That's the whole point of reference types, i.e. the value of a variable is a reference, not an object, so passing by value only copies the reference, not the object. If you are not assigning anything to that parameter inside the method then it should be declared ByVal.

If you are assigning to the parameter inside the method then the solution is to declare the method to be generic. That way, the parameter won't be Control but will actually be the type you pass in. In its simplest form, that would be:

Private Sub Procedure(Of T)(ByRef control As T)
    '...
End Sub

That's probably not enough though, because that would allow you to pass any object as an argument. To restrict the method to only accept controls:

Private Sub Procedure(Of TControl As Control)(ByRef control As TControl)
    '...
End Sub

Now you will only be able to pass a control to the method but, inside the method, the parameter will be treated as the actual type of the argument you passed, e.g. if you pass a Button then TControl is fixed to be Button. If you need to create a control of the appropriate type inside the method then you need another restriction too, which enables you to assume a parameterless constructor, e.g.

Private Sub Procedure(Of TControl As {New, Control})(ByRef control As TControl)
    control = New TControl With {.Location = New Point(100, 100),
                                 .Size = New Size(50, 25)}

    '...
End Sub

That code means that, inside the method, you know that the type of the parameter is Control or derived from that type and that you can create new instances by invoking a parameterless constructor.

Upvotes: 1

Enigmativity
Enigmativity

Reputation: 117064

Since the signature for Procedure is Sub Procedure(ByRef param As Control) and you're passing a Button to the method, the compiler is correctly warning you about an implicit conversion.

Imagine that this were the definition of Procedure:

Sub Procedure(ByRef param As Control)
    param = New Label()
End Sub

And if you called it this way:

Dim button = New Button()
Procedure(button)

Then you're effectively calling this code:

Dim button As Button = New Button()
button = New Label()

Hence the compiler warning.

If you change the signature to Sub Procedure(ByVal param As Control) then there is no possibility of assignment back to the calling variable and the warning will go away.

The use of the extra parenthesis forces the call to be ByVal instead of ByRef. See https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/procedures/how-to-force-an-argument-to-be-passed-by-value

Upvotes: 3

Related Questions