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.
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: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
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 Threading.ThreadPool.QueueUserWorkItem(Sub() Dim value = TakeLongTimeToDoSomething(5000) Me.BeginInvoke(Sub() Me.Text = value.ToString 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.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
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:-
Doesn't that look a lot more manageable and easy to understand?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
Perform complex initialization of variables
We are all familiar with variable initialization:-
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 = {0, 1, 1, 2, 3, 5, 8, 13} 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() 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
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: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
Tracking progress of a functionCode: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
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:-
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: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
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: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
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.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
Anyways, that's all I have for today. Have a good day everyone.




Reply With Quote