Custom Types Supporting Ranges
Add Index and Range support to your types.
Custom Types Supporting Ranges is a free C# Academy lesson on CoddyKit. This is 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, and your progress syncs across the web and the CoddyKit app. The C# Academy course includes 4 lessons in total.
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.
Learn C# with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 93
- Lessons
- 346
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. 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. The C# Academy course includes 4 lessons in total.
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, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.
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