Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

Almost BitFields in .NET

3.89/5 (7 votes)
4 May 2017CPOL5 min read 28.1K   107  
How to implement a quasi-bitfield in .NET

Introduction

If you have a background in C or C++ and have moved or dabbled in .NET, you may have noticed that Bitfields are missing from .NET. You can visit the suggestion site for future additions to .NET but there doesn't seem to be enough of a draw to get this implemented. In this article, I'm going to show how to build a Bitfield like structure.

Background

Those of you who have never used a Bitfield may be wondering why this might be important. A Bitfield will allow you to pack a bunch of values into one single integer. Many communication and recording structures use bitfields to group a bunch of settings into one value; a good example is the DCB structure in windows for setting up and configuring serial ports.

Typically in C, you could build a structure looking like this:

C++
//bit field box properties
struct Box{
 unsigned int opaque     :1;
 unsigned int fill_color :4;
 unsigned int something  :4; // remaining bits after this are unused
}box1;

//access the fields like this
box1.fill_color=2;
box1.opaque=1;

What's great about that is it makes it feel like an object to manipulate instead of A LOT of constants, masks and spaghetti code managing all the little bits for setting, flipping and clearing. The same thing above could look like this when doing bit twiddling (shown in VB).

VB.NET
Dim box1 as int32
Dim Const OPAQUE as int32 = &b1
Dim Const FILL_COLOR as int32=&b11110
Dim Const SOMETHING as int32=&b111100000

'set the fill color with clearing the bits first
box1=box1 and not FILL_COLOR
box1 = box1 or 2<<1

'set the opaque flag
box1=box1 or OPAQUE

This example is not bad (yet) but after a couple hundred lines of code, you start losing the meaning for OPAQUE; does it go with the box variables or does it also apply to the circle variables? Did I set that bit? Do I encapsulate the value into a whole class to manage dealing with the constants? How many spaces do I shift over for each mask, and do I make constants for those too?

This made me wish for Bitfields so many times it was crazy.

Using the Code

Since I can't have built in support for bitfields, I'll have to roll out my own using generics:

VB.NET
<CLSCompliant(False)> Public Structure BitField16_
(Of enumeration As {Structure, IComparable, IConvertible, IFormattable})
    Public Value As UInt16

    Public Sub New(initial As UInt16)
        Value = initial
    End Sub

    'gets or sets the bits depending on the enum passed
    Public Property [field](v As enumeration) As UInt16
        Get
            Dim i As Integer = [Enum].ToObject(GetType(enumeration), v)
            Return (Value And i) >> shifted(i)
        End Get
        Set(newvalue As UInt16)
            Dim i As Integer = [Enum].ToObject(GetType(enumeration), v)
            Value = Value And Not (i) 'clear any old bits
            Value = Value Or (i And (newvalue << shifted(i))) 'set any new bits
        End Set
    End Property

    'finds the first shifed bit and returns the offset that it sits at
    Private Function shifted(mask As Integer) As Byte
        Dim v As Integer = (mask And -mask) 'removes all but the LSB
        Dim c As Integer = 0 'counter
        Do Until v = 1
            v = v >> 1 'keep shifting right until the value is 1
            c += 1
        Loop
        Return c
    End Function

End Structure

I used a structure instead of a class as it will keep it on the stack and easily tossed once I'm done with the variable. The second thing you might notice is the "Of enumeration" in the structure declaration; that's right we are going to pass a Enumeration to this structure to define the fields of the bitfield.

VB.NET
<Flags>Public Enum testEnum
    bits1 = &B11
    bits2 = &B11100
    bits3 = &B100000
    bits4 = &B11000000
End Enum

Instead of letting the enum go from 0,1,2,3... internally, we set the value using "&B" binary constants so we can see the bits each mask will handle. (somedays, I wish VB would let me write "&B0000 0011" to make things line up pretty) so you can see that "bits1" can take up to two bits for a maximum value of 3 where "bits3" only has one and can only take 0 or 1. Other than that, you can make your own enum however you want, just be sure not to go over the 16 bits (or 32,64 if you change it up a bit) in your masks.

The "shifted" function will return how many positions a mask is shifted to the left, this will help decipher the value when getting or setting with the unsigned integer. Usage becomes pretty simple now:

VB.NET
Dim t as BitField16(Of testEnum)

t.field(testEnum.bits3) = 5 'can only store 0 or 1 the remaining bits get ignored
t.field(testEnum.bits2 = 5 'can store 3 bits worth so &b101 will fit just fine in that field

Yes, it's a little bit more wordy than the "C" style bitmask, but it's a lot cleaner than a bunch of constants and other mess. and the internal value can be saved streamed or whatever is needed.

Points of Interest

I chose the UInt16 (Ushort) because I handle a bunch of serialport data in my day to day work and most remote devices I work with only handle up to 16 bit data; so feel free to change it up to handle UInt32 or UInt64 type data. If you want to use any signed types for the bitfield like a Int32, just be sure your Enum masks don't use the MSB as the .NET runtime will throw a fit.

Never try to set a negative value to one of the fields as you will lose data, Bitfields can only handle positive data.

This code is not super robust at the moment as it should throw a exception if an Enum has a mask with 0 in one of the fields or if some other valuetype is passed to the generic other than an enum will cause issues, but I needed something lightweight and fairly quick; the "shifted" function I wish I could trim off cycles to make it leaner but I'm sure using higher end math would add more cycles. If you have a suggestion for the "shifted" function, I'll be happy to hear it.

The "[Enum].ToObject(GetType(enumeration), v)" casting and reflection are also a bit of performance hit, but the trade off for cleaner code I feel is worth it; as the alternative is to write specialized structures to handle each type bitfield I deal with and handle all the bit twiddling there.

In the sample code, I've also included type case operators to get/set a UInt16 directly to the structure without having to deal with the Value field directly.

Image 1

In the attached project, it will also have 4 speed tests on different types of tests shown in system ticks running on my i7 Thinkpad.

  1. The first speed test is the generic (the slowest of the bunch) but most flexible and least amount of future maintenance.
  2. The second is the non-generic test with a known Enum, much quicker, but now requires a structure per Bitfield.
  3. The third is a full structure with each field written out, quickest yet, cleaner on client code, a lot more future maintenance if new fields are needed, largest in code size.
  4. The last speed test is a base line for working with a UInt16 directly, this is the quickest because no calls to other function are being put on the stack but the most fragle of the bunch as the client code has to handle and maintain all the bits.

You can also see in all 4 cases that the size required is still only 2 bytes.

History

  • 2nd May, 2017: Initial write up
  • 3rd May, 2017: Added source code to compare speed tests
  • 4th May, 2017: Added the "Flags" attribute to the Enum, thanks BillWoodruff for pointing that out

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)