0Pricing
C# Academy · Lesson

Custom Types Supporting Ranges

Add Index and Range support to your types.

Custom Types Supporting Ranges is a free C# Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Two Ways to Support Indices and Ranges

Your own types can opt into Index and Range syntax. There are two recipes: provide explicit indexers, or provide a Length/Count property plus a Slice method.

Explicit Index Indexer

Add an indexer that takes System.Index and the ^ operator just works on your type.

using System;

class Deck {
    private int[] cards = { 1, 2, 3, 4, 5 };
    public int this[Index i] => cards[i];
}

var d = new Deck();
Console.WriteLine(d[^1]); // 5

Explicit Range Indexer

To support slicing with .., add an indexer that takes System.Range and returns the slice type you want.

using System;

class Deck {
    private int[] cards = { 1, 2, 3, 4, 5 };
    public int[] this[Range r] => cards[r];
}

var d = new Deck();
Console.WriteLine(string.Join(",", d[1..3])); // 2,3

The Length + Slice Pattern

The compiler can also synthesize range support automatically if your type has a Count or Length property and a Slice(int start, int length) method.

Implementing the Pattern

Here a custom buffer exposes Length and Slice. The compiler rewrites buf[1..3] into buf.Slice(1, 2) for you.

using System;

class Buffer {
    private int[] data = { 10, 20, 30, 40 };
    public int Length => data.Length;
    public int this[int i] => data[i];
    public int[] Slice(int start, int length) {
        var result = new int[length];
        Array.Copy(data, start, result, 0, length);
        return result;
    }
}

var buf = new Buffer();
Console.WriteLine(string.Join(",", buf[1..3])); // 20,30

Why Length Matters

The Length (or Count) property is what lets the compiler resolve from-end indices like ^1 on your type. Without it, ^ cannot be translated.

using System;

class Buffer {
    private int[] data = { 10, 20, 30, 40 };
    public int Length => data.Length;
    public int this[Index i] => data[i.GetOffset(Length)];
}

var buf = new Buffer();
Console.WriteLine(buf[^1]); // 40

Resolving Index Manually

Inside an Index indexer, call i.GetOffset(Length) to convert a possibly-from-end index into a plain offset.

using System;

class Ring {
    private string[] items = { "a", "b", "c" };
    public int Length => items.Length;
    public string this[Index i] {
        get {
            int offset = i.GetOffset(Length);
            return items[offset];
        }
    }
}

var r = new Ring();
Console.WriteLine(r[^2]); // b

Resolving Range Manually

For a Range indexer, r.GetOffsetAndLength(Length) gives you both the start and the count to copy.

using System;

class Ring {
    private int[] items = { 1, 2, 3, 4, 5 };
    public int Length => items.Length;
    public int[] this[Range r] {
        get {
            var (start, len) = r.GetOffsetAndLength(Length);
            var slice = new int[len];
            Array.Copy(items, start, slice, 0, len);
            return slice;
        }
    }
}

var r = new Ring();
Console.WriteLine(string.Join(",", r[^3..^1])); // 3,4

Count vs Length

The pattern accepts either name: arrays and spans use Length; List<T> uses Count. Either property satisfies the compiler.

using System;
using System.Collections.Generic;

var list = new List<int> { 1, 2, 3, 4 };
// List<T> has Count, so Index works:
Console.WriteLine(list[^1]); // 4

Designing Slice Return Types

Decide whether your Slice returns a copy (like arrays) or a view (like spans). Views are cheaper but share mutable state, so document the choice.

A Complete Custom Type

This Sequence supports both ^ and .. via explicit indexers and a Length property.

using System;

class Sequence {
    private int[] data;
    public Sequence(int[] d) => data = d;
    public int Length => data.Length;
    public int this[Index i] => data[i];
    public int[] this[Range r] => data[r];
}

var s = new Sequence(new[] { 5, 10, 15, 20 });
Console.WriteLine(s[^1]);                       // 20
Console.WriteLine(string.Join(",", s[1..^1]));  // 10,15

Quick Check

Confirm what enables range syntax on a custom type.

Recap

You learned two ways to add Index/Range support to your types.

  • Explicit indexers taking Index and Range.
  • The implicit pattern: a Count/Length property plus Slice(int, int).
  • GetOffset(Length) and GetOffsetAndLength(Length) resolve from-end positions.

Frequently asked questions

Is the “Custom Types Supporting Ranges” lesson free?

Yes — the full text of “Custom Types Supporting Ranges” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “Custom Types Supporting Ranges”?

Add Index and Range support to your types. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Custom Types Supporting Ranges” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The Index Type and ^ Operator
  2. The Range Type and .. Operator
  3. Ranges with Arrays and Strings
  4. Custom Types Supporting Ranges
← Back to C# Academy