BlockChain Programming and VB.NET, Part 2

It seems as if the BitCoin fad has eased up a bit, especially after these types of currencies suffered some serious blows. Be that as it may, the BlockChain is still a very interesting technology, and it won’t die soon.

In my previous article, “Introduction to Blockchain Programming,” I explained how to get started with BlockChain programs in VB. Today, let’s take it further, and create a test application. Please ensure that you have the code as explained in Part 1, because we will continue to build on to that application.

Code

Add a Module for our Extension methods:

Public Module Extensions

   <System.Runtime.CompilerServices.Extension>
   Public Function CutArray(Of T)(tSrc As T(), uStart As UInt64, _
         uLimit As UInt64) As T()

      If uLimit < 0 Then

         uLimit = BitConverter.ToUInt64(BitConverter._
            GetBytes(tSrc.LongLength), 0) + uLimit

      End If

      Dim uLength As UInt64 = uLimit - uStart

      Dim tResult As T() = New T(uLength - 1) {}

      For i As UInt64 = 0 To uLength - 1

         tResult(i) = tSrc(i + uStart)

      Next

      Return tResult

   End Function

   <System.Runtime.CompilerServices.Extension>
   Public Function NullArray(Of TSource)(ieSrc As _
         IEnumerable(Of TSource)) As Boolean

      Return ieSrc.All(Function(t) t Is Nothing)

   End Function

   Private ReadOnly HexStrings As String() = New String() {"00",
      "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a",
      "0b", "0c", "0d", "0e", "0f", "10", "11", "12", "13", "14",
      "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e",
      "1f", "20", "21", "22", "23", "24", "25", "26", "27", "28",
      "29", "2a", "2b", "2c", "2d", "2e", "2f", "30", "31", "32",
      "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c",
      "3d", "3e", "3f", "40", "41", "42", "43", "44", "45", "46",
      "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", "50",
      "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a",
      "5b", "5c", "5d", "5e", "5f", "60", "61", "62", "63", "64",
      "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e",
      "6f", "70", "71", "72", "73", "74", "75", "76", "77", "78",
      "79", "7a", "7b", "7c", "7d", "7e", "7f", "80", "81", "82",
      "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c",
      "8d", "8e", "8f", "90", "91", "92", "93", "94", "95", "96",
      "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", "a0",
      "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa",
      "ab", "ac", "ad", "ae", "af", "b0", "b1", "b2", "b3", "b4",
      "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be",
      "bf", "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8",
      "c9", "ca", "cb", "cc", "cd", "ce", "cf", "d0", "d1", "d2",
      "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc",
      "dd", "de", "df", "e0", "e1", "e2", "e3", "e4", "e5", "e6",
      "e7", "e8", "e9", "ea", "be", "ec", "ed", "ee", "ef", "f0",
      "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa",
      "fb", "fc", "fd", "fe", "ff"}

   <System.Runtime.CompilerServices.Extension>
   Public Function ConvertToHex(value As Byte) As String

      Return HexStrings(value)

   End Function

End Module

The Extension Module provides extension methods for the NullArray and CutArray methods, as well as the ConvertToHex method. Add the Message class.

Class Message

   Private ReadOnly btToken As Byte() = {34, 64, 8, 145}

   Public Message As New Dictionary(Of String, String)() From {
      {"MessageHandshake", "Handshake"},
      {"MessageDisconnect", "Disconnect"},
      {"MessagePing", "Ping"},
      {"MessagePong", "Pong"},
      {"MessageGetPeers", "Get peers"},
      {"MessagePeers", "Peers"},
      {"MessageTransactions", "Transactions"},
      {"MessageBlock", "Blocks"},
      {"MessageGetChain", "Get chain"},
      {"MessageNotInChain", "Not in chain"}
   }
End Class

The Message class contains a Message method, which is a Dictionary object that holds the various messages can be output by our Token objects.

Add the RLPDecode class:

Public NotInheritable Class RLPDecode

   Private Shared ReadOnly uOffsetShort As UInt64 = &H80

   Private Shared ReadOnly uOffsetLong As UInt64 = &HB8

   Private Shared ReadOnly uOffsetShortList As UInt64 = &HC0

   Private Shared ReadOnly uOffsetLongList As UInt64 = &HF8

   Private Shared ReadOnly uMax As UInt64 = &HFF

   Public Shared Function Decode(btData As Byte(), _
         uPos As UInt64) As Decode

      If btData Is Nothing OrElse btData.Length < 1 Then

         Return Nothing

      End If

      Dim uPrefix As UInt64 = btData(uPos) And uMax
      If uPrefix = uOffsetShort Then

         Return New Decode(uPos + 1, New Byte(-1) {})

      ElseIf uPrefix < uOffsetShort Then

         Return New Decode(uPos + 1, New Byte() {btData(uPos)})

      ElseIf uPrefix < uOffsetLong Then

         Dim len As UInt64 = uPrefix - uOffsetShort

         Return New Decode(uPos + 1 + len, btData.CutArray(uPos + _
            1, uPos + 1 + len))

      ElseIf uPrefix < uOffsetShortList Then

         Dim uLength As UInt64 = uPrefix - uOffsetLong + 1

         Dim uBytesLength As UInt64 = Converter.ConvertByteArray _
            ToUInt64(btData.CutArray(uPos + 1, uPos + 1 + uLength))

         Return New Decode(uPos + 1 + uLength + uBytesLength, _
            btData.CutArray(uPos + 1 + uLength, uPos + 1 + _
            uLength + uBytesLength))

      ElseIf uPrefix < uOffsetLongList Then

         Dim uLen As UInt64 = uPrefix - uOffsetShortList

         Dim uPrev As UInt64 = uPos

         uPos += 1

         Return DecodeList(btData, uPos, uPrev, uLen)

      ElseIf uPrefix < uMax Then

         Dim uLength As UInt64 = uPrefix - uOffsetLongList + 1

         Dim uListLength As UInt64 = Converter.ConvertByteArrayTo_
            UInt64(btData.CutArray(uPos + 1, uPos + 1 + uLength))

         uPos = uPos + uLength + 1

         Dim uPrev As UInt64 = uListLength

         Return DecodeList(btData, uPos, uPrev, uListLength)

      End If

   End Function

   Private Shared Function DecodeList(uData As Byte(), uPos As _
         UInt64, uPrev As UInt64, uLength As UInt64) As Decode

      Dim lstSllice As New List(Of [Object])()

      Dim i As UInt64 = 0

      While i < uLength

         Dim dResult As Decode = Decode(uData, uPos)

         lstSllice.Add(dResult.GetDecoded())

         uPrev = dResult.GetPos()
         i += (uPrev - uPos)

         uPos = uPrev

      End While

      Return New Decode(uPos, lstSllice.ToArray())

   End Function

   Public Shared Function StringToByteArray(strHex As String) _
         As Byte()

      If strHex.Length Mod 2 = 1 Then

         Throw New Exception("No odd number of digits")

      End If

      Dim arrTemp As Byte() = New Byte((strHex.Length >> 1) _
         - 1) {}

      For i As Integer = 0 To (strHex.Length >> 1) - 1

         arrTemp(i) = CByte((GetHexVal(Hex(i << 1)) << 4) + _
            (GetHexVal(Hex((i << 1) + 1))))

      Next

      Return arrTemp

   End Function

   Private Shared Function GetHexVal(chrHex As Char) As Integer

      Dim iVal As Integer = Val(chrHex)

      Return iVal - (If(iVal < 58, 48, 87))

   End Function

End Class

Recursive Length Prefix (RLP) encodes nested arrays of binary data.

Add the RLPEncode class:

Public NotInheritable Class RLPEncode

   Private Shared ReadOnly uength As UInt64 = UInt64.MaxValue
   Private Shared ReadOnly uSized As Integer = 56
   Private Shared ReadOnly uOffsetShortItem As Integer = &H80
   Private Shared ReadOnly uOffsetShortList As Integer = &HC0

   Public Shared Function Encode(oInput As [Object]) As Byte()

      If TypeOf oInput Is Array Then

         Dim arrInput As [Object]() = DirectCast(oInput, _
            [Object]())

         If arrInput.Length = 0 Then

            Return EncodeLength(arrInput.Length, uOffsetShortList)

         End If

         Dim btOutput As Byte() = New Byte(-1) {}

         For Each arrayObject As [Object] In arrInput

            btOutput = Encoder.JoinByteArrays(btOutput,  _
               Encode(arrayObject))

         Next

         Dim btPrefix As Byte() = EncodeLength(btOutput.Length, _
            uOffsetShortList)

         Return Encoder.JoinByteArrays(btPrefix, btOutput)

      Else

         If oInput Is Nothing Then

            Throw New ArgumentNullException("Null input")

         End If

         Dim btHexInput As Byte() = Encoder.ToHex(oInput)

         If btHexInput.Length = 1 Then

            Return btHexInput

         Else

            Dim btFirst As Byte() = EncodeLength(btHexInput.Length, _
               uOffsetShortItem)

            Return Encoder.JoinByteArrays(btFirst, btHexInput)

         End If

      End If

   End Function

   Public Shared Function EncodeLength(uLength As ULong, iOffset _
         As Integer) As Byte()

      If uLength < uSized Then

         Dim btFirst As Byte = CByte(uLength + iOffset)
         Return New Byte() {btFirst}

      ElseIf DirectCast(uLength, UInt64) < uength Then

         Dim btLength As Byte() = Encoder.ConvertUInt64ToByte _
            Array(DirectCast(uLength, UInt64))

         Dim btFirst As Byte = CByte(btLength.Length + iOffset + _
            uSized - 1)

         Return Encoder.JoinByteArrays(New Byte() {btFirst}, _
            btLength)

      End If

      Throw New Exception("Input too long")

   End Function

End Class

Together, the Decode and Encode classes deal with nested arrays of binary data. They encode and decode the structure of integers.

Everything is now set up. Now, we can add a test class library to make use of all the BlockChain functionalities.

Create a Test class for each of the previous project’s classes. Let’s start with the CompactEncoderTest class that should be added to the new Class library.

Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports HTG_Ethereum
Imports System.Text

Public Class CompactEncoderTest
   Private Shared ReadOnly T As Byte = 16
   <TestMethod>
   Public Sub TestCompactEncodeOne()
      Dim test As Byte() = New Byte() {1, 2, 3, 4, 5}
      Dim expected As Byte() = New Byte() {&H11, &H23, &H45}
      Dim result As Byte() = CompactEncoder.CompactEncode(test)
      Assert.AreEqual(Encoding.ASCII.GetString(result),
         Encoding.ASCII.GetString(expected))
   End Sub

   <TestMethod>
   Public Sub TestCompactEncodeTwo()
      Dim test As Byte() = New Byte() {0, 1, 2, 3, 4, 5}
      Dim expected As Byte() = New Byte() {&H0, &H1, &H23, _
         &H45}
      Dim result As Byte() = CompactEncoder.CompactEncode(test)
      Assert.AreEqual(Encoding.ASCII.GetString(result), _
         Encoding.ASCII.GetString(expected))
   End Sub

   <TestMethod>
   Public Sub TestCompactEncodeThree()
      Dim test As Byte() = New Byte() {0, 15, 1, 12, 11, 8,
         T}
      Dim expected As Byte() = New Byte() {&H20, &HF, _
         &H1C, &HB8}
      Dim result As Byte() = CompactEncoder.CompactEncode(test)
      Assert.AreEqual(Encoding.ASCII.GetString(result), _
      Encoding.ASCII.GetString(expected))
   End Sub

   <TestMethod>
   Public Sub TestCompactEncodeFour()
      Dim test As Byte() = New Byte() {15, 1, 12, 11, 8, T}
      Dim expected As Byte() = New Byte() {&H3F, &H1C, &HB8}
      Dim result As Byte() = CompactEncoder.CompactEncode(test)
      Assert.AreEqual(Encoding.ASCII.GetString(result), _
         Encoding.ASCII.GetString(expected))
   End Sub

   <TestMethod>
   Public Sub TestCompactHexDecode()
      Dim test As Byte() = Encoding.ASCII.GetBytes("verb")
      Dim expected As Byte() = New Byte() {7, 6, 6, 5, 7, 2,
         6, 2, 16}
      Dim result As Byte() = CompactEncoder.CompactHexDecode(test)
      Assert.AreEqual(Encoding.ASCII.GetString(result),
         Encoding.ASCII.GetString(expected))
   End Sub

   <TestMethod>
   Public Sub TestCompactDecodeOne()
      Dim test As Byte() = New Byte() {&H11, &H23, &H45}
      Dim expected As Byte() = New Byte() {1, 2, 3, 4, 5}
      Dim result As Byte() = CompactEncoder.CompactDecode(test)
      Assert.AreEqual(Encoding.ASCII.GetString(result), _
         Encoding.ASCII.GetString(expected))
   End Sub

   <TestMethod>
   Public Sub TestCompactDecodeTwo()
      Dim test As Byte() = New Byte() {&H0, &H1, &H23, _
         &H45}
      Dim expected As Byte() = New Byte() {0, 1, 2, 3, 4, 5}
      Dim result As Byte() = CompactEncoder.CompactDecode(test)
      Assert.AreEqual(Encoding.ASCII.GetString(result), _
         Encoding.ASCII.GetString(expected))
   End Sub

   <TestMethod>
   Public Sub TestCompactDecodeThree()
      Dim test As Byte() = New Byte() {&H20, &HF, &H1C,
         &HB8}
      Dim expected As Byte() = New Byte() {0, 15, 1, 12, 11, 8,
         T}
      Dim result As Byte() = CompactEncoder.CompactDecode(test)
      Assert.AreEqual(Encoding.ASCII.GetString(result), _
         Encoding.ASCII.GetString(expected))
   End Sub

   <TestMethod>
   Public Sub TestCompactDecodeFour()
      Dim test As Byte() = New Byte() {&H3F, &H1C, &HB8}
      Dim expected As Byte() = New Byte() {15, 1, 12, 11, 8, T}
      Dim result As Byte() = CompactEncoder.CompactDecode(test)
      Assert.AreEqual(Encoding.ASCII.GetString(result), _
         Encoding.ASCII.GetString(expected))
   End Sub
End Class

These send Test to interrogate the results and compares them to what was expected.

Add the Test for RLPDecoder:

Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports HTG_Ethereum
Imports System.Text

<TestClass>
Public Class RLPDecoderTest
   <TestMethod>
   Public Sub TestDecodeSingleCharacter()
      Dim test As String = "64"
      Dim expected As String = "d"
      Dim result As [Object] = Encoding.ASCII.GetString _
         (DirectCast(RLPDecode.Decode(RLPDecode.StringToByteArray _
         (test), UInt64.MinValue).GetDecoded(), Byte()))
      Assert.AreEqual(expected, result)
   End Sub

   <TestMethod>
   Public Sub TestDecodeSingleString()
      Dim test As String = "83646f67"
      Dim expected As String = "dog"
      Dim result As [Object] = Encoding.ASCII.GetString _
         (DirectCast(RLPDecode.Decode(RLPDecode.StringToByteArray _
         (test), UInt64.MinValue).GetDecoded(), Byte()))
      Assert.AreEqual(expected, result)
   End Sub

   <TestMethod>
   Public Sub TestDecodeEmptyString()
      Dim test As String = "80"
      Dim expected As String = ""
      Dim result As [Object] = Encoding.ASCII.GetString _
         (DirectCast(RLPDecode.Decode(RLPDecode.StringToByteArray _
         (test), UInt64.MinValue).GetDecoded(), Byte()))
      Assert.AreEqual(expected, result)
   End Sub

   <TestMethod>
   Public Sub TestDecodeArrayOfEmptyStrings()
      Dim test As String = "c0"
      Dim expected As String() = New String() {}
      Dim expectedBool As Boolean = (expected Is Nothing OrElse _
         expected.Length = 0)
      Dim result As [Object]() = DirectCast(RLPDecode.Decode _
         (RLPDecode.StringToByteArray(test), UInt64.MinValue) _
         .GetDecoded(), [Object]())
      Dim resultBool As Boolean = result.NullArray()
      Assert.AreEqual(expectedBool, resultBool)
   End Sub

   <TestMethod>
   Public Sub TestDecodeZero()
      Dim test As String = "80"
      Dim expected As UInt64 = 0
      Dim result As [Object] = Converter.ConvertByteArrayTo _
         UInt64(DirectCast(RLPDecode.Decode(RLPDecode.StringTo _
         ByteArray(test), UInt64.MinValue).GetDecoded(), Byte()))
      Assert.AreEqual(expected, result)
   End Sub

   <TestMethod>
   Public Sub TestDecodeLowInteger()
      Dim test As String = "0f"
      Dim expected As UInt64 = 15
      Dim result As [Object] = Converter.ConvertByteArrayTo _
         UInt64(DirectCast(RLPDecode.Decode(RLPDecode.StringTo _
         ByteArray(test), UInt64.MinValue).GetDecoded(), Byte()))
      Assert.AreEqual(expected, result)
   End Sub

   <TestMethod>
   Public Sub TestDecodeMediumInteger()
      Dim test As String = "820400"
      Dim expected As UInt64 = 1024
      Dim result As [Object] = Converter.ConvertByteArrayTo _
         UInt64(DirectCast(RLPDecode.Decode(RLPDecode.StringTo _
         ByteArray(test), UInt64.MinValue).GetDecoded(), Byte()))
      Assert.AreEqual(expected, result)
   End Sub

   <TestMethod>
   Public Sub TestDecodeBigInteger()
      Dim test As String = "88ffffffffffffffff"
      Dim expected As UInt64 = 18446744073709551615UL
      Dim result As [Object] = Converter.ConvertByteArrayTo _
         UInt64(DirectCast(RLPDecode.Decode(RLPDecode.StringTo _
         ByteArray(test), UInt64.MinValue).GetDecoded(), Byte()))
      Assert.AreEqual(expected, result)
   End Sub

   <TestMethod>
   Public Sub TestDecodeLongString()
      Dim test As String = "b8384c6f72656d20697073756d20646f6c6f _
         722073697420616d65742c20636f6e7365637465747572206164697 _
         069736963696e6720656c6974"
      Dim expected As String = "test"
      Dim result As [Object] = Encoding.ASCII.GetString(DirectCast _
         (RLPDecode.Decode(RLPDecode.StringToByteArray(test), _
         UInt64.MinValue).GetDecoded(), Byte()))
      Assert.AreEqual(expected, result)
   End Sub

   <TestMethod>
   Public Sub TestDecodeEmptyStringList()
      Dim test As String = "c0"
      Dim expected As String() = New String(-1) {}
      Dim expectedBool As Boolean = (expected Is Nothing OrElse _
         expected.Length = 0)
      Dim result As [Object]() = DirectCast(RLPDecode.Decode _
         (RLPDecode.StringToByteArray(test), UInt64.MinValue) _
         .GetDecoded(), [Object]())
      Dim resultBool As Boolean = result.NullArray()
      Assert.AreEqual(expectedBool, resultBool)
   End Sub

   <TestMethod>
   Public Sub TestDecodeShortStringList()
      Dim test As String = "cc83646f6783676f6483636174"
      Dim expected As String() = New String() {"dog", "god", "cat"}
      Dim result As [Object]() = DirectCast(RLPDecode.Decode _
         (RLPDecode.StringToByteArray(test), UInt64.MinValue) _
         .GetDecoded(), [Object]())
      Assert.AreEqual(expected(0), Encoding.ASCII.GetString _
         (DirectCast(result(0), Byte())))
      Assert.AreEqual(expected(1), Encoding.ASCII.GetString _
         (DirectCast(result(1), Byte())))
      Assert.AreEqual(expected(2), Encoding.ASCII.GetString _
         (DirectCast(result(2), Byte())))
   End Sub

   <TestMethod>
   Public Sub TestDecodeLongStringList()
      ' fails'
      Dim test As String = "f83e83636174b8384c6f72656d20697073756 _
         d20646f6c6f722073697420616d65742c20636f6e736563746574757 _
         2206164697069736963696e6720656c6974"
      Dim expected As String() = New String() {"cat", "test"}
      Dim result As [Object]() = DirectCast(RLPDecode.Decode _
         (RLPDecode.StringToByteArray(test), UInt64.MinValue) _
         .GetDecoded(), [Object]())
      Assert.AreEqual(expected(0), Encoding.ASCII.GetString _
         (DirectCast(result(0), Byte())))
      Assert.AreEqual(expected(1), Encoding.ASCII.GetString _
         (DirectCast(result(1), Byte())))
   End Sub

   <TestMethod>
   Public Sub TestDecodeMultiList()
      ' fails'
      Dim test As String = "cc01c48363617483646f67c102"
      Dim expected As [Object]() = New [Object]() {DirectCast(1, _
         UInt64), New [Object]() {"cat"}, "dog", New [Object]() _
         {DirectCast(2, UInt64)}}
      Dim result As [Object]() = DirectCast(RLPDecode.Decode _
         (RLPDecode.StringToByteArray(test), UInt64.MinValue) _
         .GetDecoded(), [Object]())
      Assert.AreEqual(expected(0), Converter.ConvertByteArrayTo _
         UInt64(DirectCast(result(0), Byte())))
      Assert.AreEqual(expected(1).ToString(), result(1).ToString())
      Assert.AreEqual(expected(2), Encoding.ASCII.GetString _
         (DirectCast(result(2), Byte())))
      Assert.AreEqual(DirectCast(expected(3), [Object]())(0), _
         Converter.ConvertByteArrayToUInt64(DirectCast(DirectCast _
         (result(3), [Object]())(0), Byte())))
   End Sub

   <TestMethod>
   Public Sub TestDecodeListOfEmptyLists()
      ' fails'
      Dim test As String = "c4c2c0c0c0"
      Dim expected As [Object]() = New [Object]() {New [Object]() _
         {New [Object]() {}, New [Object]() {}}, New [Object]() {}}
      Dim result As [Object]() = DirectCast(RLPDecode.Decode _
         (RLPDecode.StringToByteArray(test), UInt64.MinValue) _
         .GetDecoded(), [Object]())
      Assert.AreEqual(expected.NullArray(), result.NullArray())
   End Sub

   <TestMethod>
   Public Sub TestDecodeTwoListsOfEmptyLists()
      Dim test As String = "c7c0c1c0c3c0c1c0"
      Dim expected As [Object]() = New [Object]() {New [Object]() _
         {}, New [Object]() {New [Object]() {}}, New [Object]() _
         {New [Object]() {}, New [Object]() {New [Object]() {}}}}
      Dim result As [Object]() = DirectCast(RLPDecode.Decode _
         (RLPDecode.StringToByteArray(test), UInt64.MinValue) _
         .GetDecoded(), [Object]())
      Assert.AreEqual(expected.NullArray(), result.NullArray())
   End Sub
End Class

It makes use of the methods and tries to obtain the correct results.

Finally, add the Test class for the DiscReason class:

Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports HTG_Ethereum

<TestClass>
Public Class DiscReasonTest
   <TestMethod>
   Public Sub TestGetDiscReason()
      Assert.AreEqual(Disconnected.GetReason(0), "Disconnect _
         requested")
      Assert.AreEqual(Disconnected.GetReason(1), "Disconnect _
         TCP sys error")
      Assert.AreEqual(Disconnected.GetReason(6), "Disconnect _
         wrong genesis block")
      Assert.AreEqual(Disconnected.GetReason(7), "Disconnect _
         incompatible network")
      Assert.AreEqual(Disconnected.GetReason(8), "Unknown")
      Assert.AreEqual(Disconnected.GetReason(9), "Unknown")
      Assert.AreEqual(Disconnected.GetReason(100), "Unknown")
   End Sub
End Class

Conclusion

The BlockChain is a very complicated animal. It is an interesting technology, and it is still in its infancy, but knowing how it works is quite fun.

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read