C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
146: Bit Manipulation with BitOperations
If you've spent any time with low-level C#, you're probably comfortable with the basic bitwise operators—AND, OR, XOR, and shifts. They're great, but they only get you so far. When I need to do something more complex, like counting how many bits are set or finding the index of the first "1" in a sequence, I used to write these clumsy while loops. It was slow, error-prone, and honestly, a bit embarrassing given that modern CPUs have dedicated instructions for this.
That's where System.Numerics.BitOperations comes in. This class is essentially a wrapper around hardware intrinsics. It lets you write high-level C# that the JIT compiler turns into lightning-fast CPU instructions. I want to show you how to use it by building a simple StatusEffectManager for a game engine.
Mapping effects to a bitmask
Imagine we have a set of status effects (Poison, Burn, Frozen, etc.). Instead of a List<Effect>, which creates garbage for the GC to clean up every frame, we'll use a uint bitmask. Each bit represents one effect.
public enum StatusEffect { None = 0, Poison = 1 << 0, Burn = 1 << 1, Frozen = 1 << 2, Stunned = 1 << 3, Hasted = 1 << 4 } public class StatusEffectManager { private uint _activeEffects = 0; public void AddEffect(StatusEffect effect) => _activeEffects |= (uint)effect; public void RemoveEffect(StatusEffect effect) => _activeEffects &= ~(uint)effect; }Finding the first active effect
Now, let's say the game engine needs to process the most "important" effect first. Since we've ordered our enum, the lowest bit index is the highest priority. I could write a loop to check every bit, but
BitOperations.TrailingZeroCountdoes this in a single CPU cycle.public int GetFirstEffectIndex() { // This returns the number of zero bits before the first '1' // If _activeEffects is 0000 1000, it returns 3. return BitOperations.TrailingZeroCount(_activeEffects); }The "Empty Mask" trap
I actually tripped over this the first time I used this class. I wrote the code above and tested it with a few effects active. It worked perfectly. Then, I tested it with no effects active. My program crashed with an
IndexOutOfRangeException.Here is why: if the input to
TrailingZeroCountis 0, it doesn't return -1 or throw an error. It returns the total number of bits in the type. For auint, that's 32. If I'm using that result as an index into an array of effect descriptions, I'm suddenly trying to access index 32 of a 5-element array.I had to add a guard clause to handle the "empty" state correctly:
public int GetFirstEffectIndex() { if (_activeEffects == 0) return -1; return BitOperations.TrailingZeroCount(_activeEffects); }Quantifying the state with PopCount
Finally, let's say we want to know how many effects are currently active. You might be tempted to iterate through the bits, but
BitOperations.PopCount(Population Count) is the standard way to count "set" bits across the industry. It's incredibly efficient.public int GetActiveEffectCount() { // Returns the number of bits set to 1. return BitOperations.PopCount(_activeEffects); }By using
BitOperations, we've replaced potentially expensive loops with a few method calls that translate directly to assembly instructions likePOPCNTorTZCNT. It's cleaner code and significantly better performance.
📋 Practical Task
Exercise: Building a Fast Bit-Based Resource Allocator
You are building a system to manage a pool of 32 hardware resources. A uint is used as a "busy mask," where a 1 represents a busy resource and a 0 represents an available one.
Create a class ResourceAllocator with the following requirements:
- A private
uint _busyMask. - A method
int Allocate(): This should find the first available resource (the first 0 bit). UseBitOperations.LeadingZeroCountorTrailingZeroCount. Hint: You may need to invert the mask using the~operator to turn the 0s into 1s before counting. If no resources are available, return -1. - A method
void Free(int index): This should mark the resource at the given index as available (set the bit to 0). - A method
int GetUsageCount(): UseBitOperations.PopCountto return how many resources are currently busy.
There are no comments for now.