VB.NET Simplify Action(Of T) - vb.net

I am using VB.Net 4 and am successfully using a class with the following signature:
Sub Register(Of TMessage)(recipient As Object, action As
System.Action(Of TMessage))
I want to learn about lambdas in VB so I wanted to see if I can simplify my current code.
CURRENTLY: I have the following in the constructor of a class (simplified for clarity)
Public Sub New()
Dim myAction As New Action(Of String)(AddressOf HandleMessage)
Messenger.Default.Register(Of [String])(Me, myAction)
End Sub
And later in the class I have the following:
Private Function HandleMessage(sMsg As String)
If sMsg = "String Value" Then
If Me._Selection.HasChanges Or Me._Selection.HasErrors Then
Return True
End If
Return False
Else
Return False
End If
End Function
QUESTION: Is there a way to simplify this into a lambda like in C# where I don't have to declare the MyAction variable in the constructor, but just pass the string value to HandleMessage function "inline" with the register sub? (I hope that makes sense)

So your constructor code is equivalent to:
Messenger.[Default].Register(Of String)(Me,
Function(sMsg)
If sMsg = "String Value" Then
If Me._Selection.HasChanges Or Me._Selection.HasErrors Then
Return True
End If
Return False
Else
Return False
End If
End Function)
It's worth mentioning though you don't need to use lambdas just to get rid of your explicit delegate creation. This is perfectly legal too:
Messenger.[Default].Register(Of String)(Me, AddressOf HandleMessage)
There is a bit of strangeness though with your example: you've declared your Register method as taking an Action(Of TMessage) which means the function passed needs no return value. Your function however is returning a Boolean. Visual Basic here is doing the fancy "delegate relaxation" and is throwing away the return value. So all your Return True/Return False bits aren't actually doing anything special.

Related

Function '<procedurename>' doesn't return a value on all code paths. Are you m,ssing a 'Return' statement?

Consider :
Private Function isAvailableQuantity() As Boolean
Try
sqL = "SELECT StocksOnHand FROM ITEM WHERE ItemNo = " & Val(txtSearch.Text) & ""
ConnDB()
cmd = New OleDbCommand(sqL, conn)
dr = cmd.ExecuteReader(CommandBehavior.CloseConnection)
If dr.Read = True Then
If Val(txtQuantity.Text) <= dr(0) Then
isAvailableQuantity = True
Else
MsgBox("Insuficient stocks", MsgBoxStyle.Critical, "Validate Stocks")
txtSearch.Clear()
End If
End If
Catch ex As Exception
MsgBox(ex.Message)
Finally
cmd.Dispose()
conn.Close()
End Try
End Function
I don't know what to do. The older version visual studio doesn't get this error.
i am using vs 2022 right now and it seems to have an error on the contrary vs 2010 doesn't
In VB.NET, a Function is a method that returns a value and a Sub is a method that doesn't return a value. If your method doesn't need to return anything, use a Sub, e.g.
Private Sub DoSomething()
'Do some stuff here.
End Sub
If you do use a Function then there are two ways to return a value. The bad way is to assign a value to the implicit local variable that is named after the method, e.g.
Private Function DoSomething() As Boolean
'Do some stuff here.
DoSomething = True
End Function
That way basically only exists to support upgraded VB6 code. If your teacher is showing you that then they are obviously an old VB6 developer who hasn't actually learned VB.NET properly. The good way to return a value is with an explicit Return statement, e.g.
Private Function DoSomething() As Boolean
'Do some stuff here.
Return True
End Function
If there are multiple paths that execution could take through your code, you need to make sure that a value is returned on all of them. As an example, this code does not do that:
Private Function DoSomething() As Boolean
If Date.Now.DayOfWeek = DayOfWeek.Monday Then
Return True
End If
End Function
That will return True on Mondays but it doesn't return anything on other days. In this specific case, one fix would be this:
Private Function DoSomething() As Boolean
If Date.Now.DayOfWeek = DayOfWeek.Monday Then
Return True
End If
Return False
End Function
If you are going to use the bad way to return a value and you want to return a particular value on all but one code path then the logical thing to do is to set the return value at the start to the default and then only change it in that one place, e.g.
Private Function DoSomething() As Boolean
DoSomething = False
If Date.Now.DayOfWeek = DayOfWeek.Monday Then
DoSomething = True
End If
End Function
That last example is the easiest (although not best) fix for your scenario.

Why does the Default Property access the setter when removing an item

In the code below, when trying to remove an item from the Cases list the code breaks in the Setter with an index out of bounds. When running the debugger in VisualStudio 2017 it successfully goes through the Remove() function and deletes the last item but after returning to Main() it will break on the Setter and the call stack says it is coming from the Remove call. Example code below:
Sub Main()
Dim Cases As Collection = New Collection()
Dim caseIndex As Integer = 2
Cases.Remove(Cases(caseIndex))
End Sub
Public Class Collection
Public WithEvents Cases As List(Of CaseClass)
Public Sub New()
Cases = New List(Of CaseClass)()
Cases.Add(New CaseClass)
Cases.Add(New CaseClass)
Cases.Add(New CaseClass)
End Sub
Default Public Property BeltCase(ByVal Index As Integer) As CaseClass
Get
Return Cases(Index)
End Get
Set(ByVal Value As CaseClass)
Cases(Index) = Value
End Set
End Property
Public Sub Remove(ByRef BeltCase As CaseClass)
Cases.Remove(BeltCase)
End Sub
End Class
Public Class CaseClass
Public test As Int16
End Class
Call Stack:
TestingVBBug.exe!TestingVBBug.Module1.Collection.set_BeltCase(Integer Index,TestingVBBug.Module1.CaseClass Value) Line 25 Basic
TestingVBBug.exe!TestingVBBug.Module1.Main() Line 6 Basic
So why would we be going through the Setter at all. And why does that happen after we exit the remove function?
The problem is caused by your Remove() method, that is, you have a ByRef parameter (for some reason). When you use ByRef, any changes made to the parameter inside the method must be reflected to the variable that was passed to the method. That happens by reassigning the value to the original variable.
In your case, it works like this:
The Remove() method is called and a variable (Cases(caseIndex)) is passed to it.
Some work is done inside the Remove() method which might, or might not include changing the value of the parameter BeltCase.
The value of the parameter BeltCase gets reassigned to the variable that was originally passed to the method (which is Cases(caseIndex)).
As a result of the above step, the setter of the BeltCase property gets called with Index = 2 which raises the out of range exception because Cases(2) doesn't exist (was removed).
To confirm, you can see this problem go away when you replace this line:
Cases.Remove(Cases(caseIndex))
..with:
Dim myCase As CaseClass = Cases(caseIndex)
Cases.Remove(myCase)
That way, you create a new variable which refers to the same CaseClass object and most importantly avoid calling the setter of your Collection.BeltClase property.
However, a better solution would be to not use ByRef in the first place since you don't seem to need it in this situation. So, simply use Public Sub Remove(ByVal BeltCase As CaseClass) instead.
Check this question for more about ByVal and ByRef with objects.
One last thing, please don't call your class Collection because it can be very confusing to anyone looking at your project.

Cannot call Count(predicate) on list

The following code produces results that are confusing. Note I am coming from C# background and know very little VB.
Module Module1
Sub Main()
Dim list As List(Of String) = New List(Of String)()
Dim result As Integer
result = list.Count() '1
result = list.Count(Function(p) True) '2
result = CType(list, IEnumerable(Of String)).Count(Function(p) True) '3
End Sub
End Module
The confusing bits:
There is a property named Count and an extension method with the same name. By calling Count() I expect the extension method to be called, but the property is accessed. Why and how to invoke a method?
I expected extension method call, but compilation fails with BC32016 (as if property was accessed instead of method). Why is that?
This works as expected.
I think it's a limit of VB since doing this doesn't compile. A is already a property, can't have a method with the same name.
Class Test
Public Property A As Integer
Public Function A(ByVal b As String) As Integer
Return 0
End Function
End Class
You also have other options.
Enumerable.Count(list, Function(p) True)
list.AsEnumerable.Count(Function(p) True)

VB.NET 2012 Property Set on Property Get

I am having a very weird situation in VS 2012 RC VB.NET project targeting .NET 2.0. For some reason the property's Set method is called in addition to its Get method:
This works as expected:
Dim _searchparray = New Byte() {37, 115, ...}
Dim rep() As Byte = _opt.ReplaceBytes
If Arrays.CompareTo(rep, _searchparray, 1, False) = -1 AndAlso _opt.SearchMatchPlaceholderInReplaceBytes Then ...
That is _opt.ReplaceBytes's Get method is called only once, and it's Set method is not called.
But this does not work:
Dim _searchparray = New Byte() {37, 115, ...}
If Arrays.CompareTo(_opt.ReplaceBytes, _searchparray, 1, False) = -1 AndAlso _opt.SearchMatchPlaceholderInReplaceBytes Then ...
Here, first _opt.ReplaceBytes's Get method is called, then Arrays.CompareTo returns and THEN _opt.ReplaceBytes's Set method is called! Why? The call stack indicates that the caller is the last line in the sample above! But where does it set the property? It cannot be in Arrays.CompareTo because the Set method is called after the function returned a value, and it cannot be set via _opt.SearchMatchPlaceholderInReplaceBytes's Get method either, because its Get method returns the value of the underlying field and does nothing else!
Does any one have an explanation for this weird behavior?
Thanks.
Here's the entire sample project that demonstrates this:
Imports System.Runtime.CompilerServices
Module Module1
Sub Main()
Dim _opt As New Opts
Dim _searchparray = New Byte() {37, 115}
If Arrays.CompareTo(_opt.ReplaceBytes, _searchparray, 1, False) = -1 AndAlso _opt.SearchMatchPlaceholderInReplaceBytes Then
Console.WriteLine("0")
End If
Console.WriteLine("1")
End Sub
End Module
Module Arrays
<Extension()> _
Friend Function CompareTo(Of T As IEquatable(Of T))(ByRef SearchArray() As T, ByRef AnotherArray() As T, ByRef aWildCardElement As T, Optional aUseWildcards As Boolean = True) As Integer
Dim min As Integer = If(SearchArray.Length < AnotherArray.Length, SearchArray.Length, AnotherArray.Length) - 1
If aUseWildcards AndAlso aWildCardElement IsNot Nothing Then
For i = 0 To min
If SearchArray(i).Equals(aWildCardElement) Then Continue For
If Not SearchArray(i).Equals(AnotherArray(i)) Then Return i
Next
Else
For i = 0 To min
If Not SearchArray(i).Equals(AnotherArray(i)) Then Return i
Next
End If
If SearchArray.Length = AnotherArray.Length Then
Return -1
Else
Return min + 1
End If
End Function
End Module
Public Class Opts
Private _ReplaceBytes() As Byte = New Byte() {}
<Xml.Serialization.XmlIgnore()> _
Public Property ReplaceBytes As Byte()
Get
Return _ReplaceBytes
End Get
Set(ByVal value As Byte())
_ReplaceBytes = value
End Set
End Property
Private _SearchMatchPlaceholderInReplaceBytes As Boolean = False
Public Property SearchMatchPlaceholderInReplaceBytes() As Boolean
Get
Return _SearchMatchPlaceholderInReplaceBytes 'Set breakpoint here
End Get
Set(ByVal value As Boolean)
'Set breakpoint here too
_SearchMatchPlaceholderInReplaceBytes = value
End Set
End Property
End Class
Namespace Global.System.Runtime.CompilerServices
<AttributeUsage((AttributeTargets.Method Or (AttributeTargets.Class Or AttributeTargets.Assembly))), System.Reflection.Obfuscation(ApplyToMembers:=True, Exclude:=True)> _
Public NotInheritable Class ExtensionAttribute
Inherits Attribute
Public Sub New()
End Sub
End Class
End Namespace
This is an interaction between the ByRef declaration and passing a property as the argument. This is forbidden in C# but the VB.NET compiler works around the problem.
By declaring the argument ByRef, you tell the compiler that you might modify the passed object reference. Which is fine if you pass a local variable as the method argument, that local variable gets updated when your code assigns the argument. But this is a problem when you pass a property, such an assignment would have to call the property setter. Which in turn invalidates the passed argument. Which can cause a very difficult to diagnose bug.
The C# compiler just forbids this due to the bug possibilities. The VB.NET compiler however works around it by ensuring that the setter gets called after the method stops executing. Which is exactly what you saw with the debugger. Trouble is, it always calls the setter, even if you didn't modify the argument.
The workaround is obvious, using ByRef is just a bug. Your method does not actually assign the SearchArray argument. The argument needs to be ByVal.
It seems that in VB.NET, when you pass a property of Array type by reference, it is copied back at the end of the function. This makes sense because passing it ByRef means that the reference might have changed inside the function. Since it's a property that has been passed by reference, we affect the (possibly changed) reference to the property's setter.
The solution would be to pass it by value instead (ByVal). You have no need to pass it by reference in your code. In fact, in most cases, it is better to pass it by value than by reference. Only use ByRef if a parameter also acts as a return value.

VB.NET equivalent of a nameless variable in C#?

In C#, you can do this:
new MyClass().MyMethod();
The method is executed and the reference is (typically) discarded since no reference to the object is kept.
My question is: Is this possible with VB.NET (.NET v4)?
Edit: I suppose this is a better example:
new Thread((x) => doSomething()).Start();
Is this even possible in VB.NET?
VB.NET has stricter rules about the syntax of a statement. The curly-brace languages allow any expression to also be a statement, simply by terminating it with a semi-colon. That's not the case for VB.NET. You can only use this syntax if the method you call is a Function. Which allows you to use the assignment statement:
Dim result = New Test().Func()
If it is a Sub then you'll have to use the assignment statement to store the object reference. This otherwise has no runtime effect, the reference is optimized away.
In addition to Hans' answer, you could use a With statement:
Sub Main
With New Person("Ahmad")
.PrintName()
.Name = "Mageed"
.PrintName()
End With
End Sub
Public Class Person
Public Property Name As String
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
Public Sub PrintName()
Console.WriteLine("Name: {0} - Len: {1}", Name, Name.Length)
End Sub
End Class
It's not as succinct as C#, but the reference to the object is discarded after End With.
If you're explicitly wanting to call a sub and not a function, you can:
Call (New obj).Func()
Which will anonymously create a new obj, and call its Func() method
You can do lambda functions in VB.NET like this:
Dim test = Function (x)
x.doStuff()
End Function
Which would be semantically equivilent to:
var test = (x) => x.doStuff();
I think the one constraint though is that it must return a result under VB.NET.