Table of Contents

Combine text, an enum, and a union

Intermediate · C#. The enum describes a kind; it does not automatically choose a union member. The application decides which interpretation makes sense.

Run this example

Prerequisites: the repository's .NET 10 SDK and a checkout of this source. Run from the repository root:

dotnet run --project docs/examples/CStructSharp.Docs.Examples.csproj -c Release -- composite-record

The runner checks Text, AB with a zero character, and exact six-byte round trip. Success includes PASS composite-record.

This example uses the C# API. Browser capabilities and result shapes are described in the browser guide.

Complete program

The layout, options, input bytes, helper methods, and required types are all included. To adapt it outside the repository, create a .NET 10 console project, add CStructSharp, and replace Program.cs with this complete file. These examples follow the source version; use a matching package when testing a release.

Download the complete C# source.

// Generated from executable documentation examples. Edit the source region, then regenerate.
using System;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Buffers;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using CStructSharp;
using CStructSharp.Codecs;
using CStructSharp.Diagnostics;
using CStructSharp.Introspection;
using CStructSharp.Values;

internal static partial class Program
{
    public static void Main()
    {
        CompositeRecord();
        Console.WriteLine("PASS composite-record");
    }

    private static void CompositeRecord()
    {
        const string definition = """
            enum kind : uint8 { Text = 1, Numbers = 2 };
            union payload_word { uint8 small; uint16 large; };
            struct record {
                kind type;
                char label[3];
                payload_word payload;
            };
            """;
        var layout = new CStruct(definition);
        byte[] bytes = [0x01, 0x41, 0x42, 0x00, 0x34, 0x12];
        StructValue record = layout.Parse(bytes, "record");

        EnumValueResult type = record.Get<EnumValueResult>("type");
        Equal("Text", type.Name);
        Equal("AB\0", record.Get<string>("label"));

        UnionValue payload = record.Get<UnionValue>("payload");
        Equal((byte)0x34, payload.Get<byte>("small"));
        Equal((ushort)0x1234, payload.Get<ushort>("large"));
        SequenceEqual(bytes, layout.Serialize("record", record));
    }

    private static void Equal<T>(T expected, T actual)
    {
        if (!EqualityComparer<T>.Default.Equals(expected, actual))
        {
            throw new InvalidOperationException($"Expected '{expected}', received '{actual}'.");
        }
    }

    private static void SequenceEqual(byte[] expected, byte[] actual)
    {
        if (!expected.AsSpan().SequenceEqual(actual))
        {
            throw new InvalidOperationException(
                $"Expected {Convert.ToHexString(expected)}, received {Convert.ToHexString(actual)}.");
        }
    }
}

Try it and diagnose mistakes

Inspect both union member values for 34 12.

Answer: small sees 52; large sees 4660. They overlap the same storage. The program contains assertions for its original inputs. When changing an input intentionally, update the expected assertion too; an unchanged assertion is not evidence that the new value is wrong.

Continue with the related guide or choose another recipe.