I just wanted to share some small tips and tricks I've personally been using as of late in VB.Net that makes life just a little bit easier for certain things. These are not big or revolutionary in any way. Just some very minor things that I arrived at as a natural progression of continual use of VB.Net.
Make any function asynchronous.
Code:
Public Class Form1
Private Function TakeLongTimeToDoSomething(ByVal x As Integer) As Double
Dim d As Double = CInt(x)
For i = 1 To 10
d = d / 9
Threading.Thread.Sleep(500)
Next
Return d
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.Text = TakeLongTimeToDoSomething(5000)
End Sub
End Class
Look at the above code. You press a button and it executes some long running task after which it returns a value. The problem with the above is that it locks up the UI while it's performing the task. Now your first instinct might be to use multi-threading. You might do something like this:-
Code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Threading.ThreadPool.QueueUserWorkItem(Sub()
Dim value = TakeLongTimeToDoSomething(5000)
Me.BeginInvoke(Sub()
Me.Text = value.ToString
End Sub)
End Sub)
End Sub
Now the above works but it's a little wordy, don't you think? You also have an additional problem in that the button could now be pressed multiple times before the first one completes. You might think to yourself that you could disable the button during the time the work is being done:-
Code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Threading.ThreadPool.QueueUserWorkItem(Sub()
Dim value = TakeLongTimeToDoSomething(5000)
Me.BeginInvoke(Sub()
Me.Text = value.ToString
Button1.Enabled = True
End Sub)
End Sub)
End Sub
This works but look at the structure. That is ugly. Firstly it's a lot of boilerplate and now with the disabling and enabling of the buttons you have related things spread between the event handler and the worker thread. That is very difficult to follow and if it gets more complicated, it would become error prone.
So how can we simplify all this? Well you could use a combination of Tasks and Async/Await. All of that could be simplified to this:-
Code:
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Me.Text = Await Task.Run(Function() TakeLongTimeToDoSomething(5000).ToString)
Button1.Enabled = True
End Sub
Doesn't that look a lot more manageable and easy to understand?
Perform complex initialization of variables
We are all familiar with variable initialization:-
Code:
Public Class Form2
Private _fib = {0, 1, 1, 2, 3, 5, 8, 13}
End Class
The above code initializes an array variable with the Fibonacci sequence. Simple right? But what if we wanted to initialize it with a calculation instead. We could do this:-
Code:
Public Class Form2
Private _fib As Integer()
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim a = 0I, b = 1I, c = 0I
Dim l As New List(Of Integer)
l.AddRange({a, b})
For i = 2 To 7
c = a + b
l.Add(c)
a = b
b = c
Next
_fib = l.ToArray
End Sub
End Class
Perfectly reasonable. But now we have separated the initialization from the declaration. What if you started adding more functions to that class and pushed Form2_Load to somewhere in the middle or bottom? Every time you wanted to know how that array was initialized you'd have to wade through a bunch of code to find Form2_Load and to make matters worse, what if you decided to put all initialization into a function that is called by the Load event? That is another level of indirection you'd have to trace. Wouldn't it be nice if we could perform this initialization where it was declared to keep everything more organized? Well you can do this by clever use of an anonymous function. You could do this:-
Code:
Public Class Form2
Private _fib As Integer() = (Function()
Dim a = 0I, b = 1I, c = 0I
Dim l As New List(Of Integer)
l.AddRange({a, b})
For i = 2 To 7
c = a + b
l.Add(c)
a = b
b = c
Next
Return l.ToArray
End Function).Invoke()
End Class
Now the initialization and the declaration of the array are one unit. This one looks a bit ugly due to how much code there is to calculate a Fibonacci sequence but in practice it won't always be this verbose. You'd mostly use it to set properties on classes where the class constructor doesn't provide adequate means to do so. Here's an actual example from a working program:-
Code:
Private client As HttpClient = (Function()
Dim c As New HttpClient
c.DefaultRequestHeaders.UserAgent.Add(New Headers.ProductInfoHeaderValue("NiyaScaper", "1.0"))
Return c
End Function).Invoke
Tracking progress of a function
This one is my favorite. Let's say you have a function like a file download where you need to track the progress. Look at this:-
Code:
Public Class Form3
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Await Task.Run(Sub() DownloadFile())
MessageBox.Show("Download complete!")
Button1.Enabled = True
End Sub
Private Sub DownloadFile()
Dim r As New Random
For i = 1 To 100
Threading.Thread.Sleep(r.Next(100, 300))
Next
End Sub
End Class
The above is a mock-up of a downloader. You click the Button and it starts an asynchronous download. When the download is finished, we are informed through a message box. But what if we wanted to track the progress of the download? We might do this:-
Code:
Private Sub DownloadFile()
Dim r As New Random
For i = 1 To 100
Me.Invoke(Sub()
ProgressBar1.Value = i
End Sub)
Threading.Thread.Sleep(r.Next(100, 300))
Next
End Sub
We changed our DownloadFile function to report progress to a ProgressBar. This is actually very bad because we have now married our download function to the UI itself. If we wanted to take that function and dump it into it's own class or use it in another project, we must now untangle it from the UI. Good news is, we can unmarry this function from the UI through the use of delegates:-
Code:
Public Class Form3
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Await Task.Run(Sub() DownloadFile(Sub(i As Integer)
Me.Invoke(Sub()
Me.ProgressBar1.Value = i
End Sub)
End Sub))
MessageBox.Show("Download complete!")
Button1.Enabled = True
End Sub
Private Sub DownloadFile(Optional ByVal reportProgress As Action(Of Integer) = Nothing)
Dim r As New Random
For i = 1 To 100
If reportProgress IsNot Nothing Then reportProgress.Invoke(i)
Threading.Thread.Sleep(r.Next(100, 300))
Next
End Sub
End Class
Now DownloadFile is capable of reporting progress without being married to the UI. You can now freely move that function to wherever you like. The UI code will take responsibility for updating the progress bar as it should. DownloadFile should not even know that a UI exists.
Anyways, that's all I have for today. Have a good day everyone.