Click here to Skip to main content
15,867,307 members
Articles / Programming Languages / C#

Bit Field in C# using struct

Rate me:
Please Sign up or sign in to vote.
4.64/5 (26 votes)
10 May 2016CPOL5 min read 95.8K   1.3K   28   40
Generic C# equivalent of the bit field struct in C

 

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.

C#
/// <summary>
/// Specifies the number of bits in the bit field structure
/// Maximum number of bits are 64
/// </summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = false)]
public sealed class BitFieldNumberOfBitsAttribute : Attribute
{
    /// <summary>
    /// Initializes new instance of BitFieldNumberOfBitsAttribute with the specified number of bits
    /// </summary>
    /// <param name="bitCount">The number of bits the bit field will contain (Max 64)</param>
    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;
    }

    /// <summary>
    /// The number of bits the bit field will contain
    /// </summary>
    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.

C#
/// <summary>
/// Specifies the length of each bit field
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public sealed class BitFieldInfoAttribute : Attribute
{
    /// <summary>
    /// Initializes new instance of BitFieldInfoAttribute with the specified field offset and length
    /// </summary>
    /// <param name="offset">The offset of the bit field</param>
    /// <param name="length">The number of bits the bit field occupies</param>
    public BitFieldInfoAttribute(byte offset, byte length)
    {
        Offset = offset;
        Length = length;
    }

    /// <summary>
    /// The offset of the bit field
    /// </summary>
    public byte Offset { get; private set; }

    /// <summary>
    /// The number of bits the bit field occupies
    /// </summary>
    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.

/// <summary>
/// Interface used as a marker in order to create extension methods on a struct
/// that is used to emulate bit fields
/// </summary>
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#
C++
struct example_bit_field
{
    unsigned char bit1 : 1;
    unsigned char bit2 : 1;
    unsigned char two_bits : 2;
    unsigned char four_bits : 4;
}
C#
[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.

C#
 /// <summary>
 /// Converts the members of the bit field to an integer value.
 /// </summary>
 /// <param name="obj">An instance of a struct that implements the interface IBitField.</param>
 /// <returns>An integer representation of the bit field.</returns>
 public static ulong ToUInt64(this IBitField obj)
 {
    ulong result = 0;

    // Loop through all the properties
    foreach (PropertyInfo pi in obj.GetType().GetProperties())
    {
        // Check if the property has an attribute of type BitFieldLengthAttribute
        BitFieldInfoAttribute bitField;
        bitField = (pi.GetCustomAttribute(typeof(BitFieldInfoAttribute)) as BitFieldInfoAttribute);
        if (bitField != null)
        {
            // Calculate a bitmask using the length of the bit field
            ulong mask = 0;
            for (byte i = 0; i < bitField.Length; i++)
                mask |= 1UL << i;

            // This conversion makes it possible to use different types in the bit field
            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.

C#
/// <summary>
/// This method converts the struct into a string of binary values.
/// The length of the string will be equal to the number of bits in the struct.
/// The least significant bit will be on the right in the string.
/// </summary>
/// <param name="obj">An instance of a struct that implements the interface IBitField.</param>
/// <returns>A string representing the binary value of tbe bit field.</returns>
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.

C#
/// <summary>
/// Creates a new instance of the provided struct.
/// </summary>
/// <typeparam name="T">The type of the struct that is to be created.</typeparam>
/// <param name="value">The initial value of the struct.</param>
/// <returns>The instance of the new struct.</returns>
public static T CreateBitField<T>(ulong value) where T : struct
{
    // The created struct has to be boxed, otherwise PropertyInfo.SetValue
    // will work on a copy instead of the actual object
    object boxedValue = new T();

    // Loop through the properties and set a value to each one
    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);
        }
    }
    // Unboxing the object
    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.

C#
Console.WriteLine("First bit field ...");

ExampleBitField bitField1 = new ExampleBitField();

bitField1.Bit1 = true;
bitField1.Bit2 = 0;
bitField1.TwoBits = 0x2;     // 10
bitField1.FourBits = 0x7;    // 0111

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:

Output from execution test

 

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

 

 

 

License

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


Written By
Architect
Philippines Philippines
I have worked with mobile phone testing in manufacturing for 20 years.
Originally from Sweden, but moved to China in 2012.
At the moment I am on a sabbatical year in the Philippines.

Over the years I have used various programming languages such as Pascal, C, C++, C#, SQL, XML, XSLT.

Comments and Discussions

 
AnswerRe: Won't help maintainability Pin
George Jonsson12-May-16 3:36
professionalGeorge Jonsson12-May-16 3:36 
Question[My vote of 2] Unnecesary complexity Pin
JLuis Estrada10-May-16 18:44
JLuis Estrada10-May-16 18:44 
AnswerRe: [My vote of 2] Unnecesary complexity Pin
George Jonsson10-May-16 18:49
professionalGeorge Jonsson10-May-16 18:49 
QuestionPerformance Issues Pin
frankazoid5-May-16 8:55
frankazoid5-May-16 8:55 
AnswerRe: Performance Issues Pin
George Jonsson5-May-16 14:30
professionalGeorge Jonsson5-May-16 14:30 
GeneralRe: Performance Issues Pin
frankazoid6-May-16 1:50
frankazoid6-May-16 1:50 
GeneralRe: Performance Issues Pin
George Jonsson6-May-16 16:26
professionalGeorge Jonsson6-May-16 16:26 
GeneralRe: Performance Issues Pin
Philippe Mori11-May-16 6:46
Philippe Mori11-May-16 6:46 
If performance does not matters much for an application, the one would use byte, ushort, uint and other integral types. You would use more memory but you need to have a lot of item for that to matters.
Philippe Mori

QuestionCouldn't you just use Enums? Pin
Matthew Dennis2-May-16 9:02
sysadminMatthew Dennis2-May-16 9:02 
AnswerRe: Couldn't you just use Enums? Pin
George Jonsson2-May-16 14:46
professionalGeorge Jonsson2-May-16 14:46 
SuggestionBitVector32 Pin
Izhar A.27-Apr-16 10:20
Izhar A.27-Apr-16 10:20 
GeneralRe: BitVector32 Pin
George Jonsson27-Apr-16 11:38
professionalGeorge Jonsson27-Apr-16 11:38 
GeneralRe: BitVector32 Pin
JLuis Estrada10-May-16 18:42
JLuis Estrada10-May-16 18:42 
AnswerRe: BitVector32 Pin
George Jonsson10-May-16 19:14
professionalGeorge Jonsson10-May-16 19:14 
GeneralRe: BitVector32 Pin
Philippe Mori12-May-16 4:17
Philippe Mori12-May-16 4:17 
GeneralMy vote of 5 Pin
Dmitriy Gakh27-Apr-16 4:47
professionalDmitriy Gakh27-Apr-16 4:47 
GeneralRe: My vote of 5 Pin
George Jonsson27-Apr-16 11:42
professionalGeorge Jonsson27-Apr-16 11:42 
GeneralRe: My vote of 5 Pin
Dmitriy Gakh27-Apr-16 18:55
professionalDmitriy Gakh27-Apr-16 18:55 
GeneralRe: My vote of 5 Pin
George Jonsson27-Apr-16 19:11
professionalGeorge Jonsson27-Apr-16 19:11 
Generalinformative Pin
Southmountain26-Apr-16 17:33
Southmountain26-Apr-16 17:33 
GeneralRe: informative Pin
George Jonsson26-Apr-16 18:07
professionalGeorge Jonsson26-Apr-16 18:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.