Recognize a format before reading it
Intermediate · C#. A magic is the value that names a format. It is checked first, Matches decides from it alone without building a value, and a failure carries the label and message the layout gave it. The #define becomes a constant of the generated class.
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 -- recognize-format
The runner checks Matches true for the ext magic 53 EF, false for a wrong or cut-off magic; the failure carries the label ext.magic; the runtime layout agrees. Success includes PASS recognize-format.
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()
{
RecognizeFormat();
Console.WriteLine("PASS recognize-format");
}
/// <summary>Recognizes a format by its magic with Matches, and reads the failure of a wrong magic.</summary>
private static void RecognizeFormat()
{
byte[] ext = [0x10, 0x00, 0x00, 0x00, 0x53, 0xEF, 0x01, 0x00];
byte[] other = [0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00];
// Matches decodes only the magic: no value is built and nothing is thrown, for wrong or short input alike.
True(Disk.Matches(ext), "the magic is there");
True(!Disk.Matches(other), "the magic is wrong");
True(!Disk.Matches(ext.AsSpan(0, 5)), "the magic is cut off");
Equal(0xEF53, Disk.EXT_MAGIC);
// A broken magic is reported with the label and message the layout gave it.
True(!Disk.TryParse(other, out _, out CStructException? failure), "the read stops at the magic");
var constraint = (CStructConstraintException)failure!;
Equal("ext.magic", constraint.Label);
Equal("not an ext superblock", constraint.Description);
// A layout compiled at run time answers the same question.
var layout = new CStruct(Disk.Definition);
True(layout.Matches("superblock", ext), "same answer at run time");
True(!layout.Matches("superblock", other), "same answer at run time");
}
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);
}
}
// A magic names the format: it is checked first, and Matches answers from it alone. The #define becomes the constant Disk.EXT_MAGIC.
/// <summary>A layout whose magic names the format and whose #define is a constant of the class.</summary>
[CStructLayout("#define EXT_MAGIC 0xEF53\nstruct superblock { uint32 inodes; uint16 magic @magic(magic == EXT_MAGIC, \"ext.magic\", \"not an ext superblock\"); uint16 state @expect(state <= 2, \"ext.state\"); };", Root = "superblock", Surface = GeneratedSurface.Reader)]
public static partial class Disk
{
}
}
Try it and diagnose mistakes
Change the first state byte to 05 and call TryParse on the matching bytes.
Answer: Matches stays true, because it checks only the magic; TryParse fails with the label ext.state, because the state is out of range. 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.