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]); // 5Explicit 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,3The 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,30Why 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]); // 40Resolving 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]); // bResolving 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,4Count 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]); // 4Designing 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,15Quick 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
IndexandRange. - The implicit pattern: a
Count/Lengthproperty plusSlice(int, int). GetOffset(Length)andGetOffsetAndLength(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
- The Index Type and ^ Operator
- The Range Type and .. Operator
- Ranges with Arrays and Strings
- Custom Types Supporting Ranges