วิธีการใช้งาน Bit Fields ใน C#: คู่มือที่ครอบคลุม

การจัดการกับโครงสร้างข้อมูลแบบกะทัดรัดอย่างมีประสิทธิภาพเป็นความท้าทายทั่วไปในโปรแกรมมิ่ง โดยเฉพาะเมื่อทำงานกับการสื่อสารข้อมูลหรือโครงสร้างไฟล์ระดับต่ำ ใน C# ขณะที่ไม่มีการสนับสนุนแบบเนทีฟสำหรับ bit fields เหมือนใน C แต่เราสามารถบรรลุฟังก์ชันการทำงานที่คล้ายกันได้ด้วยการใช้แอตทริบิวต์และการสะท้อน(Reflection) อย่างชาญฉลาด บทความนี้จะพาคุณไปสู่วิธีการที่มีประสิทธิภาพในการใช้งานและจัดการกับ bit fields ใน C#

ปัญหา

สมมุติว่าคุณมีชุดของโครงสร้างที่ต้องการแสดงข้อมูลที่มีหลายแฟล็กบูลีน (หรือบิต) การใช้วิธีการธรรมดาของ C# อาจทำให้ยุ่งเหยิงและส่งผลให้โค้ดอ่านยาก ตัวอย่างเช่น บิตต่อไปนี้ที่ต้องมีการแสดง:

  • bit0 - original_or_copy
  • bit1 - copyright
  • bit2 - data_alignment_indicator
  • bit3 - PES_priority
  • bit4-bit5 - PES_scrambling control
  • bit6-bit7 - reserved

จุดประสงค์คือการจับโครงสร้างนี้ในลักษณะที่สะอาดและดูแลรักษาง่าย โดยไม่ต้องพึ่งพาการจัดการบิตด้วยตนเองในทุกครั้ง

วิธีแก้ปัญหา

ขั้นตอนที่ 1: สร้างแอตทริบิวต์ความยาวของ Bitfield

ก่อนอื่น ให้เรากำหนดแอตทริบิวต์ที่กำหนดเองที่เรียกว่า BitfieldLengthAttribute แอตทริบิวต์นี้จะเก็บความยาวของแต่ละ bit field นี่คือวิธีที่คุณสามารถตั้งค่าได้:

[global::System.AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
sealed class BitfieldLengthAttribute : Attribute
{
    uint length;

    public BitfieldLengthAttribute(uint length)
    {
        this.length = length;
    }

    public uint Length { get { return length; } }
}

ขั้นตอนที่ 2: กำหนดคลาสการแปลงพื้นฐาน

ถัดไปเราต้องการวิธีการแปลงแอตทริบิวต์ของเราให้เป็นการแสดงผลจริงของ bit field เราสามารถสร้างคลาสสถิตสำหรับตรรกะการแปลง:

static class PrimitiveConversion
{
    public static long ToLong<T>(T t) where T : struct
    {
        long r = 0;
        int offset = 0;

        foreach (System.Reflection.FieldInfo f in t.GetType().GetFields())
        {
            object[] attrs = f.GetCustomAttributes(typeof(BitfieldLengthAttribute), false);
            if (attrs.Length == 1)
            {
                uint fieldLength  = ((BitfieldLengthAttribute)attrs[0]).Length;
                long mask = (1 << (int)fieldLength) - 1;

                r |= ((UInt32)f.GetValue(t) & mask) << offset;
                offset += (int)fieldLength;
            }
        }

        return r;
    }
}

ขั้นตอนที่ 3: สร้างโครงสร้างข้อมูลของคุณ

ตอนนี้คุณสามารถสร้างโครงสร้างที่แสดงถึง bit fields ของคุณได้ นี่คือตัวอย่าง:

struct PESHeader
{
    [BitfieldLength(2)]
    public uint reserved;

    [BitfieldLength(2)]
    public uint scrambling_control;

    [BitfieldLength(1)]
    public uint priority;

    [BitfieldLength(1)]
    public uint data_alignment_indicator;

    [BitfieldLength(1)]
    public uint copyright;

    [BitfieldLength(1)]
    public uint original_or_copy;
}

ขั้นตอนที่ 4: ตัวอย่างการใช้งาน

ให้เราทำให้ทุกอย่างรวมกันในโปรแกรมง่ายๆ ที่ใช้โครงสร้างและวิธีการแปลงที่กล่าวถึงข้างต้น:

public class MainClass
{
    public static void Main(string[] args)
    {
        PESHeader p = new PESHeader
        {
            reserved = 3,
            scrambling_control = 2,
            data_alignment_indicator = 1
        };

        long l = PrimitiveConversion.ToLong(p);

        for (int i = 63; i >= 0; i--)
        {
            Console.Write(((l & (1L << i)) > 0) ? "1" : "0");
        }

        Console.WriteLine();
    }
}

คิดอย่างครอบคลุม

ด้วยการใช้แอตทริบิวต์ที่กำหนดเองและการสะท้อนใน C# คุณสามารถเลียนแบบพฤติกรรมของ bit fields จาก C ขณะที่ยังคงทำให้โค้ดของคุณอ่านและดูแลรักษาง่ายขึ้น วิธีการนี้ไม่เพียงแต่ช่วยลดความซับซ้อนของโค้ด แต่ยังทำให้สนับสนุนโครงสร้างหลายตัวได้ง่ายขึ้นโดยไม่มีการทำซ้ำอย่างมาก ดังนั้นครั้งถัดไปที่คุณต้องทำงานกับการแสดงข้อมูลแบบกะทัดรัด ให้พิจารณาใช้วิธีนี้เพื่อหาคำตอบที่มีประสิทธิภาพและเชิงสุนทรียศาสตร์!