Table of Contents

Read through an allocation-free view

Intermediate · C#. A readonly ref struct view decodes each member when it is read and allocates nothing; a short source fails with the same message the runtime reports.

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 -- generated-views

The runner checks a view reading kind 2 and length 6 from the span, ToObject producing the class, and the runtime's short-read message for four bytes. Success includes PASS generated-views.

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()
    {
        GeneratedViews();
        Console.WriteLine("PASS generated-views");
    }

    private static void GeneratedViews()
    {
        byte[] bytes = [0x02, 0x00, 0x06, 0x00, 0x00, 0x00];

        // A view decodes each member straight from the span when it is read; nothing is allocated for the header.
        var view = new Wire.HeaderView(bytes);
        Equal((ushort)2, view.Kind);
        Equal(6u, view.Length);
        Equal(6, view.Bytes.Length);

        // A view can become an object when one is wanted after all.
        Wire.Header header = view.ToObject();
        Equal(6u, header.Length);

        // Too few bytes fail the way the runtime fails: the same message, offset 0, path 'header'.
        try
        {
            _ = new Wire.HeaderView(bytes.AsSpan(0, 4));
            True(false, "a short source must fail");
        }
        catch (CStructReadException error)
        {
            Equal("Not enough bytes: needed 6, available 4 (path 'header', offset 0).", error.Message);
        }
    }

    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 True(bool condition, string message)
    {
        if (!condition)
        {
            throw new InvalidOperationException(message);
        }
    }

    // The layout text lives in the attribute; the generator adds the members of this partial class at build time.
    [CStructLayout("struct header { uint16 kind; uint32 length; };")]
    public static partial class Wire
    {
    }
}

Try it and diagnose mistakes

Read the view from a slice that starts one byte late.

Answer: The view still needs six bytes, so a five-byte slice fails with 'needed 6, available 5'; a six-byte slice from offset 1 decodes shifted values (kind 0x0600). 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.