VB.NET Why is this subroutine declared this way? - vb.net

VB.NET 2010, .NET 4
I have a basic question: I have a subroutine that I found somewhere online declared thusly:
Public Sub MyFunction(Of T As Control)(ByVal Control As T, ByVal Action As Action(Of T)) ...
I'm wondering about the (Of T As Control) part of the declaration after the sub's name. I see that T is used later in specifying the type of Control and in Action(Of T), but why is it done this way instead of just doing:
Public Sub MyFunction(ByVal Control As Control, ByVal Action As Action(Of Control)) ...
What does that part after the sub's name mean? What is its purpose? Thanks a lot and sorry for my ignorance.

That is VB.NET's generic method declaration syntax:
A generic type is a single programming
element that adapts to perform the
same functionality for a variety of
data types. When you define a generic
class or procedure, you do not have to
define a separate version for each
data type for which you might want to
perform that functionality.
An analogy is a screwdriver set with
removable heads. You inspect the screw
you need to turn and select the
correct head for that screw (slotted,
crossed, starred). Once you insert the
correct head in the screwdriver
handle, you perform the exact same
function with the screwdriver, namely
turning the screw.

(Of T) is a generic type parameter, adding As Control constrains the type of T to inherit from Control. You could write the method the second way, but you'd probably end up having to cast the Control to whatever inherited type, within the lambda expression in the Action, or in the body of MyFunction. Generics allow you to avoid that.
For example:
Sub Main()
Dim form As New Form()
Dim textBox As New TextBox
Dim listBox As New ListBox
MyFunction(textBox, Sub(c) c.Text = "Hello")
MyFunction(listBox, Sub(c) c.Items.Add("Hello"))
MyFunction2(textBox, Sub(c) c.Text = "Hello")
MyFunction2(listBox, Sub(c) CType(c, ListBox).Items.Add("Hello"))
End Sub
Public Sub MyFunction(Of T As Control)(ByVal Control As T, ByVal Action As Action(Of T))
Action(Control)
End Sub
Public Sub MyFunction2(ByVal Control As Control, ByVal Action As Action(Of Control))
Action(Control)
End Sub
It doesn't look too valuable in trivial cases, but it's invaluable for more complex cases.

As others have said, it's a constrained generic parameter. But no one has yet addressed this part of your question:
why is it done this way
The answer is in the action. If it were just declared as a Control, you wouldn't be able to do something like this, because not all controls have a .Text property*:
MyFunction(MyTextBox, Function(t) t.Text = "new value" )
The body of the function just needs to know that it's working on a control of some kind, but the Action(Of T) you pass to the function might want to know the actual type of the control.
Yes, all controls do have a .Text property. Let's pretend for a moment that some didn't

Related

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?
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
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.

Is there a way to determine the value of a property on a form that calls a method in a seperate class library

Specifically aimed at winforms development.
I suspect that the answer to this is probably No but S.O. has a nice way of introducing me to things I didn't know so I thought that I would ask anyway.
I have a class library with a number of defined methods therein. I know from personal experimentation that it is possible to get information about the application within which the class library is referenced. What I would like to know is whether it would be possible to get information about the value of a property of a control on a form when a routine on that form calls a method in my class library without passing a specific reference to that form as a parameter of the method in the class library?
So purely as an example (because it's the only thing I can think of off the top of my head). Is there a way that a message box (if it had been so designed to do so in the first place) could 'know' from which form a call to it had been made without that form being specifically referenced as a parameter of the message box in the first place?
Thanks for any insights you might have.
To address the example of the MessageBox, in many of the cases you can use the active form. You can retrieve it by using Form.ActiveForm. Of course, as regards the properties that you can request, you are limited to the properties provided by the Form or an interface that the Form implements and that the method in the other assembly also knows. To access other properties you can use Reflection, but this approach would neither be straightforward nor would it be clean.
In a more general scenario, you could provide the property value to the method as a parameter. If it is to complex to retrieve the value of the property and the value is not needed every time, you can provide a Func(Of TRESULT) to the method that retrieves the value like this (sample for an integer property):
Public Sub DoSomethingWithAPropertyValue(propValFunc As Func(Of Integer))
' Do something before
If propertyValueIsNeeded Then
Dim propVal = propValFunc()
End If
' Do something afterwards
End Sub
You call the method like this:
Public Sub SubInForm()
Dim x As New ClassInOtherAssembly()
x.DoSomethingWithAPropertyValue(Function() Me.IntegerProperty)
End Sub
I kind of question your intentions. There's no problem sending the information to a function or the constructor.
Instead of giving the information to the class, the class would ask for the information instead using an event.
Module Module1
Sub Main()
Dim t As New Test
AddHandler t.GetValue, AddressOf GetValue
t.ShowValue()
Console.ReadLine()
End Sub
Public Sub GetValue(ByRef retVal As Integer)
retVal = 123
End Sub
End Module
Class Test
Public Delegate Sub DelegateGetValue(ByRef retVal As Integer)
Public Event GetValue As DelegateGetValue
Public Sub ShowValue()
Dim val As Integer
RaiseEvent GetValue(val)
Console.WriteLine(val)
End Sub
End Class

Why is it legal to pass "Me" ByRef in VB.NET?

I was shocked just a moment ago to discover that the following is legal (the C# equivalent is definitely not):
Class Assigner
''// Ignore this for now.
Public Field As Integer
''// This part is not so weird... take another instance ByRef,
''// assign it to a different instance -- stupid but whatever. '
Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
''// But... what's this?!?
Sub AssignNew()
''// Passing "Me" ByRef???
Assign(Me, New Assigner)
End Sub
''// This is just for testing.
Function GetField() As Integer
Return Me.Field
End Function
End Class
But what's even stranger just as strange to me is that it doesn't seem to do what I expect:
Dim a As New Assigner With {.Field = 10}
a.AssignNew()
Console.WriteLine(a.GetField())
The above outputs "10," not "0" like I thought it would (though naturally, this expectation was itself infused with a certain kind of horror). So it seems that you can pass Me ByRef, but the behavior is somehow overridden (?) by the compiler to be as if you had passed Me ByVal.
Why is it legal to pass Me ByRef? (Is there some backwards-compatibility explanation?)
Am I correct in saying that the behavior of doing this is overridden by the compiler? If not, what am I missing?
This behavior actually follows pretty directly from the Visual Basic specification.
11.4.3 Instance Expressions
An instance expression is the keyword Me, MyClass, or MyBase. An instance expression, which may only be used within the body of a non-shared method, constructor, or property accessor, is classified as a value.
9.2.5.2 Reference Parameters
If the type of the variable being passed to a reference parameter is not compatible with the reference parameter's type, or if a non-variable is passed as an argument to a reference parameter, a temporary variable may be allocated and passed to the reference parameter. The value being passed in will be copied into this temporary variable before the method is invoked and will be copied back to the original variable (if there is one) when the method returns.
(All emphasis mine)
So, the compiler will create a temporary variable assigned to the value of Me to be passed as the ByRef parameter. Upon return, no copy of the resulting value will take place since Me is not a variable.
It appears the compiler transforms "Me" into a variable which is then passed ByRef. If you compile your code, then open it with Reflector, you can see what's happening:
Class Assigner
''// Methods
Public Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
Public Sub AssignNew()
Dim VB$t_ref$S0 As Assigner = Me
Me.Assign((VB$t_ref$S0), New Assigner)
End Sub
Public Function GetField() As Integer
Return Me.Field
End Function
''// Fields
Public Field As Integer
End Class
So it looks like when you call AssignNew(), you are assigning the new instance to the internally generated variable. The "a" variable doesn't get touched because it's not even a part of the function.
This is just one of the thousands of possible 'almost errors' a programmer can make. MS caught most of them, in fact, sometimes I'm suprised at how many warnings do come up.
they missed this one.
As far as why it doesn't change 'me', it's a darn good thing! When you use 'me', it just passes a copy of the real class you are working with, for safety purposes. If this worked they way you were hoping, we would be talking GIANT side-effect. You're innocently working away with in your class' methods, and them BAM all of a sudden you are in an ENTIRELY different object! That would be awful! If you're going to do that, you might as well just write a piece of spagetti MS-Basic line-numbered code with all globals that get randomly set, and no subs/functions.
The way this works is the same way if you pass arguments in parenthesis. For example this works as expected:
Assign(Reference_I_Want_To_Set, New Assigner)
But this doesn't change anything:
Assign((Reference_I_Want_To_Set), New Assigner)
If you reflect the above type of code as adam101 suggests you will see similar results. While that is huge frustration with the parenthesis, it is a very good thing with Me !!!
what you need to do to make this code work is this:
Class Assigner
''// Ignore this for now.
Private newPropertyValue As Integer
Public Property NewProperty() As Integer
Get
Return newPropertyValue
End Get
Set(ByVal value As Integer)
newPropertyValue = value
End Set
End Property
''// This part is not so weird... take another instance ByRef,
''// assign it to a different instance -- stupid but whatever. '
Shared Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
''// But... what's this?!?
Shared Sub AssignNew(ByRef x As Assigner)
''// Passing "Me" ByRef???
Assign(x, New Assigner)
End Sub
End Class
then use it like
Dim a As New Assigner With {.NewProperty = 10}
Assigner.AssignNew(a)
my understanding is you cannot change the reference of the object while using it, so you need to change it in a shared sub
since Me cannot be the target of an assignment, the code seem to create a copy of it and from that point on, your not using the real object, but a copy of it

Passing List (Of ChildClass) as parameter to method expecting List (Of ParentClass)?

I have implemented inheritance for two parent classes called Table and Field. Each parent class has several child classes. In the Table parent class, I created a method that expects parameter List(Of Field).
I get an error when trying to pass in parameter List(Of ChildField) to the method that expects a parameter of List(Of Field). The error message is as below:
Value of type
'System.Collections.Generic.List(Of
com.hlb.icisbatch.data.ChildField)'
cannot be converted to
'System.Collections.Generic.List(Of
com.hlb.icisbatch.data.Field)'
My question, is it possible to pass in list of child class as parameter? If it is not a list then it works fine. But somehow it is not possible with lists?
Below is sample class structure:
Table Field
| |
ChildTable ChildField
I have a method in the parent class:
Public Class Table
Public Sub New()
End Sub
Public Overridable Sub setFields(ByVal list As List(Of Field)
'Do work here'
End Sub
End Class
and method in child class:
Public Class ChildTable
Public Sub New(ByVal list As List(Of ChildField)
setFields(ChildField)
End Sub
End Class
No, this isn't possible - and for good reason. Consider what would happen if the calling code tried to add an instance of Field (or some other derived class) to the list. Bad stuff would happen.
In .NET 4 (and presumably VB10) this is possible via covariance and contravariance of generic delegates and interfaces (not classes) - but only where it's safe. So IList(Of T) is not variant (because values go "in" and "out" of the API); IEnumerable(Of T) is covariant (so there's a conversion from IEnumerable(Of String) to IEnumerable(Of Object)) and IComparer(Of T) is contravariant (so there's a conversion from IComparer(Of Object) to IComparer(Of String) for example).
(This ability has actually been in the CLR since v2.0, but it's only surfaced in the languages and framework for .NET 4.)
So, for .Net 4.0+ (possibly earlier versions too) you'd have to alter your setFields method:
Public Overridable Sub setFields(As T Of Field)(ByVal list As List(Of T)
'Do work here'
End Sub
Then when you call it with:
Public Sub New(ByVal list As List(Of ChildField)
setFields(list)
End Sub
it is essentially calling:
setFields(Of ChildField)(list)
which means that the setFields knows it is dealing with a list of ChildField objects and won't let anyone do anything naughty like add a different ChildNumber2Field object.
Although generics were introduced in .Net 2.0, I'm not sure exactly how advanced that version was wrt doing this kind of thing.

Action(Of T) in Visual Basic in List(Of T).ForEach

I have searched high and low for documentation on how to use this feature. While the loop I could write would be simple and take no time, I really would like to learn how to use this.
Basically I have a class, say, Widget, with a Save() sub that returns nothing. So:
Dim w as New Widget()
w.Save()
basically saves the widget. Now let's say I have a generic collection List(Of Widget) name widgetList(Of Widget) and I want to run a Save() on each item in that list. It says I can do a
widgetList.ForEach([enter Action(Of T) here])
....but how in the F does this work??? There is no documentation anywhere on the intrablags. Help would be much much appreciated.
well, I'm really outdated now... :-) but in VB it's:
widgetList.ForEach(Sub(w) w.Save())
or, more complicated:
widgetList.ForEach(New Action(Of Widged)(Sub(w As Widged) w.Save()))
If you're using VB9 (VS2008) I don't think you'll be able to use an anonymous function easily - as far as I'm aware, anonymous functions in VB9 have to be real functions (i.e. they have to return a value) whereas Action<T> doesn't return anything. C# 2's anonymous methods and C# 3's lambda expressions are more general, which is why you'll see loads of examples using List<T>.ForEach from C# and very few using VB :(
You could potentially write a MakeAction wrapper which takes a Function<T,TResult> and returns an Action<T>, but I suspect other restrictions on VB9 anonymous functions would make this impractical.
The good news is that VB10 has much more anonymous function support. (C#4 and VB10 are gaining each other's features - I believe MS is trying to go for language parity from now on, to a larger extent than before.)
Until then, to use List<T>.ForEach you'll need to write an appropriate Sub and use AddressOf to create a delegate from it. Here's a small example:
Imports System
Imports System.Collections.Generic
Public Class Test
Shared Sub Main()
Dim names as New List(Of String)
names.Add("Jon")
names.Add("Holly")
names.ForEach(AddressOf PrintMe)
End Sub
Shared Sub PrintMe(ByVal text as String)
Console.WriteLine(text)
End Sub
End Class
new Action(Of T)(AddressOf Widget.Save)
is OK if Widget got a public function called Save.
All others comment are false when I try it.
Assuming that VB does not support lambda expressions, you can create an instance of the Action(of T) delegate in VB using this syntax:
new Action(Of T)(AddressOf Widget.Save)
The below should work although I'm not up to speed on VB.Net so you may need to adjust accordingly.
widgetList.ForEach(w => w.Save())