Introduction
If you have a background in C or C++ and have moved or dabbled in .NET, you may have noticed that Bitfield
s 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:
struct Box{
unsigned int opaque :1;
unsigned int fill_color :4;
unsigned int something :4; }box1;
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).
Dim box1 as int32
Dim Const OPAQUE as int32 = &b1
Dim Const FILL_COLOR as int32=&b11110
Dim Const SOMETHING as int32=&b111100000
box1=box1 and not FILL_COLOR
box1 = box1 or 2<<1
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 Bitfield
s 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:
<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
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)
Value = Value Or (i And (newvalue << shifted(i)))
End Set
End Property
Private Function shifted(mask As Integer) As Byte
Dim v As Integer = (mask And -mask)
Dim c As Integer = 0
Do Until v = 1
v = v >> 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.
<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:
Dim t as BitField16(Of testEnum)
t.field(testEnum.bits3) = 5
t.field(testEnum.bits2 = 5
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, Bitfield
s 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.
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.
- The first speed test is the generic (the slowest of the bunch) but most flexible and least amount of future maintenance.
- The second is the non-generic test with a known
Enum
, much quicker, but now requires a structure per Bitfield
. - 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.
- 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