Start Debugging

Индексаторы расширения в C# 15 завершают члены расширения в .NET 11 Preview 6

Индексаторы расширения появились в .NET 11 Preview 6 и позволяют добавлять доступ через this[...] к типам, которыми вы не владеете. Они завершают историю членов расширения, начатую методами и свойствами в C# 14.

.NET 11 Preview 6 вышел 2026-07-14, и в разделе C# примечаний к выпуску спрятана та часть, которая наконец делает члены расширения завершёнными: индексаторы расширения. Методы расширения существуют начиная с C# 3.0, свойства расширения появились вместе с блоком расширения в C# 14, а теперь вы можете добавить доступ через this[...] к типу, которым не владеете.

Чего не хватало до сих пор

Очевидным обходным путём всегда был обычный метод расширения. Если вам нужна была семантика индекса с конца для IReadOnlyList<T>, у которого нет индексатора, понимающего Index, вы писали что-то вроде этого:

public static class ReadOnlyListExtensions
{
    public static T At<T>(this IReadOnlyList<T> list, Index index)
        => list[index.GetOffset(list.Count)];
}

// call site
IReadOnlyList<string> log = ["start", "work", "done"];
Console.WriteLine(log.At(^1));   // done
Console.WriteLine(log.At(^2));   // work

Это работает, но место вызова читается как log.At(^1) вместо синтаксиса log[^1], который даёт любой другой похожий на список тип. Вы теряете нотацию квадратных скобок, и теряете её также в списковых шаблонах.

Вариант с индексатором расширения

C# 15 позволяет объявить индексатор внутри блока extension ровно так же, как вы объявили бы индексатор экземпляра:

public static class ReadOnlyListExtensions
{
    extension<T>(IReadOnlyList<T> list)
    {
        public T this[Index index] => list[index.GetOffset(list.Count)];
    }
}

// call site
IReadOnlyList<string> log = ["start", "work", "done"];
Console.WriteLine(log[^1]);   // done
Console.WriteLine(log[^2]);   // work

Получатель, захваченный заголовком extension<T>(IReadOnlyList<T> list), находится в области видимости внутри тела, поэтому индексатор напрямую перенаправляет к list. Место вызова теперь использует настоящий синтаксис индексатора, и, поскольку компилятор трактует его как индексатор, он участвует в списковых шаблонах и выражениях диапазона так же, как встроенный.

Индексаторы расширения не ограничены формой с одним параметром и только чтением. Они поддерживают несколько параметров и аксессоры get и set, поэтому вы можете спроецировать двумерный поиск на плоское хранилище или предоставить доступную для записи проекцию над типом, который предлагает только доступ через методы.

Как включить

Это по-прежнему предварительная возможность языка, поэтому она не активируется в версии языка по умолчанию. Добавьте следующее в свой .csproj:

<LangVersion>preview</LangVersion>

Индексаторы расширения — последний из трёх классических видов членов, добравшийся до блоков расширения, а значит, ментальная модель теперь единообразна: методы, свойства и индексаторы объявляются одинаково внутри extension(...). Если вы прибегали к типам-обёрткам или неуклюжим вспомогательным методам .At(), чтобы обойти отсутствующий индексатор, Preview 6 — это выпуск, который позволяет их удалить. Полное описание смотрите в разделе C# примечаний к выпуску Preview 6.

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Назад