File-local types

C# 11.0 .NET 7.0

Published Updated Author Jeffrey T. Fritz Reading time

File-local types (file class, file record, and similar declarations) keep implementation-only types visible to one source file.

Introduced in C# 11.0, the file modifier lets you define top-level types that are accessible only within the current file. This keeps helper types close to the code they support without exposing them across the assembly.

Why it matters

File-local types improve maintainability when a helper is too large for a nested private type but still should not be part of your shared surface area. They reduce namespace clutter and make intent explicit: this type exists only to support this file.

Cautions

File-local types are not visible outside their file, so avoid them for anything shared across multiple classes or test projects. Also keep naming clear: because file-local and public types can live together, concise names with obvious responsibilities prevent confusion during refactors.

Declaring helper types with the file modifier

File-local types keep implementation details in the same file while preventing those types from leaking into project-wide namespaces.

Valid since C# 11.0

using System;

public sealed class UserCache
{
    public string CreateCacheKey(int userId, DateTime snapshotUtc)
    {
        var key = new UserCacheKey(userId, snapshotUtc);
        return key.ToString();
    }
}

file readonly record struct UserCacheKey(int UserId, DateTime SnapshotUtc)
{
    public override string ToString() => $"user:{UserId}:snapshot:{SnapshotUtc:yyyyMMddHHmmss}";
}

public static class Program
{
    public static void Main()
    {
        var cache = new UserCache();
        string key = cache.CreateCacheKey(42, new DateTime(2026, 7, 10, 9, 30, 0, DateTimeKind.Utc));
        Console.WriteLine(key);
    }
}

From nested private classes to file-local collaborators

Move deeply nested helper types out of containing classes while keeping them hidden from other files through the file modifier.

Valid since C# 11.0

Without var

using System;
using System.Collections.Generic;
using System.Globalization;

public sealed class ReportFormatter
{
    public string Format(IReadOnlyList<decimal> values)
    {
        var rowFormatter = new RowFormatter();
        return rowFormatter.Format(values);
    }

    private sealed class RowFormatter
    {
        public string Format(IReadOnlyList<decimal> values)
        {
            return string.Join(", ", values).Replace(",", CultureInfo.InvariantCulture.NumberFormat.NumberGroupSeparator);
        }
    }
}

public static class Program
{
    public static void Main()
    {
        var formatter = new ReportFormatter();
        Console.WriteLine(formatter.Format(new[] { 12.5m, 48.3m, 105.7m }));
    }
}

With var

using System;
using System.Collections.Generic;
using System.Globalization;

public sealed class ReportFormatter
{
    public string Format(IReadOnlyList<decimal> values)
    {
        var rowFormatter = new RowFormatter();
        return rowFormatter.Format(values);
    }
}

file sealed class RowFormatter
{
    public string Format(IReadOnlyList<decimal> values)
    {
        var formatted = new List<string>(values.Count);
        foreach (decimal value in values)
        {
            formatted.Add(value.ToString("N1", CultureInfo.InvariantCulture));
        }

        return string.Join(" | ", formatted);
    }
}

public static class Program
{
    public static void Main()
    {
        var formatter = new ReportFormatter();
        Console.WriteLine(formatter.Format(new[] { 12.5m, 48.3m, 105.7m }));
    }
}

Learn more

File-local types (Microsoft Learn)