Introduction
If you have to convert C or C++ code into C# you will probably sooner or later encounter bit fields.
Unfortunately C# doesn't have an off-the-shelf solution for this, so you end up starting to scratch your head and trying to figure out the best way to implement this in another way. (With as little work involved as possible, of course)
One way of implementation is to create a class or struct that has a property for each field and some methods that that can convert the structure to and from an integer value, but if you have many bit field structures to convert this work soon becomes tedious.
Another way is use a struct (or class) and create a few generic extension methods that do the conversion job for any structure.
Background
I had the need to implement code that emulated a bit field as I used a legacy function that returned an uint
that represented a C-style bit field. In order to manipulate this value and send it back, it was preferrable not to use bit wise operations and instead implement a wrapper class that made it easier and more understandable to set individual fields.
As this was not the first time I had to do such a thing, I decided to try to find a generic way in order to avoid the work of implementing specific conversion methods every time.
When searching the topic, I came upon this Stack Overflow question which addressed the same problem I had and the accepted answer plus the comments inspired me to write the classes described in this tip. So part of the credit should go to Adam Wright and Kevin P Rice.
Follow this link to see the answer: Bit fields in C#
Explaining the code
Basic Concept
The goal is to mimic the functionality of a bit field structure in C with an implementation in C#.
This is done by writing some custom attributes that can be applied to the C# struct and also some extension methods used to convert to and from an integer value and also to convert the value to a string with the base-2 representation of the value.
As with any other implementation it can be done in many different ways. I have chosen to use:
struct
instead of class
, mainly because a struct is a value type and can be used as the type for a generic method. - properties instead of fields, because it is possible to add validation of assigned values if necessary or preferred.
Also, in my implementation I have limited the number of bits to 64 by using the type UInt64
internally. This seemed like an acceptable limitation for most cases I have encountered.
Custom Attributes
Two custom attributes are required for this implementation. One is for the struct itself and one will be used on the properties.
Struct Attribute
The following custom attribute specifies the number of bits used by the whole struct.
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = false)]
public sealed class BitFieldNumberOfBitsAttribute : Attribute
{
public BitFieldNumberOfBitsAttribute(byte bitCount)
{
if ((bitCount < 1) || (bitCount > 64))
throw new ArgumentOutOfRangeException("bitCount", bitCount,
"The number of bits must be between 1 and 64.");
BitCount = bitCount;
}
public byte BitCount { get; private set; }
}
Property Attribute
This custom attribute is used on every property that represents a member in the bit field. It specifies how many bits each property can contain. The reason to have both an offset and a length is that according to MSDN the method Type.GetProperties() doesn't return the properties in a defined order.
Quote:
The GetProperties method does not return properties in a particular order, such as alphabetical or declaration order. Your code must not depend on the order in which properties are returned, because that order varies.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public sealed class BitFieldInfoAttribute : Attribute
{
public BitFieldInfoAttribute(byte offset, byte length)
{
Offset = offset;
Length = length;
}
public byte Offset { get; private set; }
public byte Length { get; private set; }
}
Marker Interface
In order to create generic extension methods for structs or classes you need to have base class or interface that is used as the type for the instance object passed to the extension method.
In this case an empty interface is created.
public interface IBitField { }
The struct
you declare must implement the interface above.
Example Bit Field
Now it is time to show a simple example.
Below is a bit field in C and the C# equivalent shown side by side.
C | C# |
struct example_bit_field
{
unsigned char bit1 : 1;
unsigned char bit2 : 1;
unsigned char two_bits : 2;
unsigned char four_bits : 4;
}
|
[BitFieldNumberOfBitsAttribute(8)]
struct ExampleBitField : IBitField
{
[BitFieldInfo(0, 1)]
public bool Bit1 { get; set; }
[BitFieldInfo(1, 1)]
public byte Bit2 { get; set; }
[BitFieldInfo(2, 2)]
public byte TwoBits { get; set; }
[BitFieldInfo(4, 4)]
public byte FourBits { get; set; }
}
|
The offset starts at zero and the value for a new property is calculated by adding the offset and length for the previous property.
For example:
A new property added after the last one will have the offset of 4 + 4 = 8.
Note: If a new property is added, don't forget to change the BitFieldNumberOfBitsAttribute
.
Extension Methods
ToUInt64
This extension method is used to convert the bit field into an ulong
. It loops through all properties and sets the corresponding bits.
public static ulong ToUInt64(this IBitField obj)
{
ulong result = 0;
foreach (PropertyInfo pi in obj.GetType().GetProperties())
{
BitFieldInfoAttribute bitField;
bitField = (pi.GetCustomAttribute(typeof(BitFieldInfoAttribute)) as BitFieldInfoAttribute);
if (bitField != null)
{
ulong mask = 0;
for (byte i = 0; i < bitField.Length; i++)
mask |= 1UL << i;
ulong value = Convert.ToUInt64(pi.GetValue(obj));
result |= (value & mask) << bitField.Offset;
}
}
return result;
}
ToBinaryString
This method is useful for presenting the bit field struct in a UI or for debugging purposes.
public static string ToBinaryString(this IBitField obj)
{
BitFieldNumberOfBitsAttribute bitField;
bitField = (obj.GetType().GetCustomAttribute(typeof(BitFieldNumberOfBitsAttribute)) as BitFieldNumberOfBitsAttribute);
if (bitField == null)
throw new Exception(string.Format(@"The attribute 'BitFieldNumberOfBitsAttribute' has to be
added to the struct '{0}'.", obj.GetType().Name));
StringBuilder sb = new StringBuilder(bitField.BitCount);
ulong bitFieldValue = obj.ToUInt64();
for (int i = bitField.BitCount - 1; i >= 0; i--)
{
sb.Append(((bitFieldValue & (1UL << i)) > 0) ? "1" : "0");
}
return sb.ToString();
}
Creator Method
This is not an extension method, but a generic static method that can be used to initialize the bit field with a default value.
public static T CreateBitField<T>(ulong value) where T : struct
{
object boxedValue = new T();
foreach (PropertyInfo pi in boxedValue.GetType().GetProperties())
{
BitFieldInfoAttribute bitField;
bitField = (pi.GetCustomAttribute(typeof(BitFieldInfoAttribute)) as BitFieldInfoAttribute);
if (bitField != null)
{
ulong mask = (ulong)Math.Pow(2, bitField.Length) - 1;
object setVal = Convert.ChangeType((value >> bitField.Offset) & mask, pi.PropertyType);
pi.SetValue(boxedValue, setVal);
}
}
return (T)boxedValue;
}
Using the code
It is pretty straight forward to declare a struct and use the extension methods.
The structure is already created above, and below you can see how to use the code.
Console.WriteLine("First bit field ...");
ExampleBitField bitField1 = new ExampleBitField();
bitField1.Bit1 = true;
bitField1.Bit2 = 0;
bitField1.TwoBits = 0x2;
bitField1.FourBits = 0x7;
ulong bits = bitField1.ToUInt64();
Console.WriteLine("ulong: 0x{0:X2}", bits);
string s = bitField1.ToBinaryString();
Console.WriteLine("string: {0}", s);
Console.WriteLine();
Console.WriteLine("Second bit field ...");
ExampleBitField bitField2 = BitFieldExtensions.CreateBitField<ExampleBitField>(0xA3);
Console.WriteLine("ulong: 0x{0:X2}", bitField2.ToUInt64());
Console.WriteLine("string: {0}", bitField2.ToBinaryString());
Execution Output
The output from the code above will look like this:
Points of Interest
One thing to consider when chosing between a class or a struct is that a struct is called by value and a class is called by reference when an instance of the bit field is sent as an argument into another method. This means that if you use a struct, a copy of it will be sent and any manipulations of the members will be lost when returning from the method.
A way around this "problem" is to use boxing.
See this MSDN article for more information on the subject: Boxing and Unboxing (C# Programming Guide)
Performance Warning
As this approach uses reflection it is not the fastest way to do it. This has been pointed out by frankazoid in the comment section.
So if you your task is to read and convert data from a communication protocol, in. for example, a repetive loop you should be aware of that this method is at least a factor 10000 slower than direct bit mask operations. Basically we are talking about several milliseconds to convert the struct to and from an integer value. However, setting and getting indvidual fields is not a problem.
If the purpose is to just read or write values to the bit field structure in, for example, an application setting, the convenience might overweigh the time penalty.
History
Revision | Date | Description |
1 | 2016-04-26 | First release of the tip |
2 | 2016-05-11 | Added a performance warning |