Skip to content

C# - Bitwise Operators

Bitwise operators in C# perform operations on integer types at the binary level. These operators are fundamental for low-level programming tasks, such as performance optimization, working with hardware, implementing encryption algorithms, and manipulating flag enums.

Bitwise operators manipulate the individual bits of their operands. To understand them, it’s essential to think of numbers in their binary representation (base-2).

The following table lists the bitwise operators supported by C#. Assume variable a holds 60 (binary 0011 1100) and variable b holds 13 (binary 0000 1101):

OperatorDescriptionExample
&Binary AND: Copies a bit to the result if it exists in both operands.(a & b) results in 12 (binary 0000 1100)
|Binary OR: Copies a bit to the result if it exists in either operand.(a | b) results in 61 (binary 0011 1101)
^Binary XOR (Exclusive OR): Copies a bit to the result if it is set in one operand but not both.(a ^ b) results in 49 (binary 0011 0001)
~Binary Ones’ Complement: A unary operator that flips the bits of its operand.(~a) results in -61. This is because integral types use Two’s Complement representation for negative numbers.
<<Left Shift: Moves the bits of the left operand to the left by the number of positions specified by the right operand. New bits on the right are filled with zeros.a << 2 results in 240 (binary 1111 0000)
>>Right Shift: Moves the bits of the left operand to the right. If the operand is a signed type, the sign bit is used to fill the new bits on the left (arithmetic shift). If unsigned, zeros are used.a >> 2 results in 15 (binary 0000 1111)

Understanding Bitwise Operations with Examples

Section titled “Understanding Bitwise Operations with Examples”

Let’s explore each operator with modern C# code examples. These examples use top-level statements, available in C# 9 and later, for conciseness.

Example: Bitwise AND (&), OR (|), and XOR (^)

Section titled “Example: Bitwise AND (&), OR (|), and XOR (^)”
int a = 5; // Binary: 0101
int b = 3; // Binary: 0011
// Bitwise AND: 0101 & 0011 = 0001 (Decimal 1)
int andResult = a & b;
Console.WriteLine($"Bitwise AND: {andResult}"); // Output: Bitwise AND: 1
// Bitwise OR: 0101 | 0011 = 0111 (Decimal 7)
int orResult = a | b;
Console.WriteLine($"Bitwise OR: {orResult}"); // Output: Bitwise OR: 7
// Bitwise XOR: 0101 ^ 0011 = 0110 (Decimal 6)
int xorResult = a ^ b;
Console.WriteLine($"Bitwise XOR: {xorResult}"); // Output: Bitwise XOR: 6

The complement operator can be tricky. It inverts all bits. For an int (a 32-bit signed integer), the value 5 is 0000...00000101. Inverting it results in 1111...11111010. In C#‘s two’s complement system, this binary pattern represents -6.

int a = 5; // Binary (32-bit): 00000000 00000000 00000000 00000101
int result = ~a;
Console.WriteLine($"Bitwise NOT: {result}"); // Output: Bitwise NOT: -6

Example: Left Shift (<<) and Right Shift (>>)

Section titled “Example: Left Shift (<<) and Right Shift (>>)”

Shift operators are an efficient way to multiply or divide by powers of 2.

// Left Shift (equivalent to multiplying by 2^n)
int num = 5; // Binary: 00000101
int leftShifted = num << 2; // Shift left by 2 bits: 00010100 (Decimal 20)
Console.WriteLine($"Left Shift (5 << 2): {leftShifted}"); // Output: Left Shift (5 << 2): 20
// Right Shift (equivalent to dividing by 2^n)
int anotherNum = 20; // Binary: 00010100
int rightShifted = anotherNum >> 2; // Shift right by 2 bits: 00000101 (Decimal 5)
Console.WriteLine($"Right Shift (20 >> 2): {rightShifted}"); // Output: Right Shift (20 >> 2): 5

A common and powerful use of bitwise operators in modern C# is managing a set of options or permissions using an enum decorated with the [Flags] attribute.

[Flags]
enum Permissions
{
None = 0, // Binary: 0000
Read = 1 << 0, // Binary: 0001 (1)
Write = 1 << 1, // Binary: 0010 (2)
Execute = 1 << 2, // Binary: 0100 (4)
All = Read | Write | Execute // Binary: 0111 (7)
}
// Assign multiple permissions using OR
Permissions userPermissions = Permissions.Read | Permissions.Write;
Console.WriteLine($"User permissions: {userPermissions}");
// Check for a permission using AND
if ((userPermissions & Permissions.Read) == Permissions.Read)
{
Console.WriteLine("User has Read permission.");
}
// Add a permission using OR
userPermissions |= Permissions.Execute;
Console.WriteLine($"After adding Execute: {userPermissions}");
// Remove a permission using AND with NOT
userPermissions &= ~Permissions.Write;
Console.WriteLine($"After removing Write: {userPermissions}");
ScenarioPitfallSolution / Best Practice
Operator PrecedenceBitwise operators have lower precedence than comparison operators. x & y == 0 is evaluated as x & (y == 0).Always use parentheses to ensure correct order of operations, e.g., (x & y) == 0.
Signed vs. UnsignedThe ~ and >> operators can produce unexpected results with signed numbers due to two’s complement and arithmetic shifting.Be explicit about using signed (int, long) or unsigned (uint, ulong) types based on your needs. Use unsigned types if you are purely manipulating bit patterns.
Magic NumbersUsing raw integer literals for masks (e.g., user.Permissions & 4) makes code hard to read and maintain.Use [Flags] enums or named constants to define bitmasks for clarity and type safety.