Extension methods and tuples
Add methods to existing types without changing them, and group values without declaring a type.
10 min read
You will learn
- Write and call extension methods
- Understand how LINQ relies on extension methods
- Create, name and deconstruct tuples
- Return several values from a method with a tuple
Extension methods: new methods for existing types
You can't edit string — it belongs to .NET. But you can add methods to it that look as if they were built in:
static class StringExtensions
{
public static string Shout(this string text) => text.ToUpper() + "!";
public static int WordCount(this string text) =>
text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
Console.WriteLine("hello".Shout()); // HELLO!
Console.WriteLine("the quick brown fox".WordCount()); // 4The recipe:
- A static class (any name;
XxxExtensionsby convention). - A static method whose first parameter has
thisbefore its type. - Call it on any value of that type, like an instance method.
What's really happening
An extension method is just a static method with nicer syntax. The compiler rewrites
"hello".Shout()into
StringExtensions.Shout("hello")So extension methods can't access private members, and they only "exist" where their namespace is imported with using.
LINQ is built on extension methods
Every LINQ method you'll use in Track 4 — Where, Select, OrderBy, Sum, Max — is an extension method on IEnumerable<T>, defined in the System.Linq namespace:
int[] numbers = { 5, 3, 8 };
Console.WriteLine(numbers.Max()); // arrays don't have a Max method — LINQ adds itThat's why all collection types get them at once: they all implement IEnumerable<T>, and the extensions target the interface.
Use them judiciously
Extension methods are great for small, general helpers and for fluent APIs (builder.AddLogging().AddDatabase()). Don't use them to bolt business logic onto types you own — just add a normal method instead.
Tuples: lightweight groups of values
Sometimes you need to pass a couple of values together without defining a class or record. A tuple does that:
var point = (3, 4);
Console.WriteLine(point.Item1); // 3
var person = (Name: "Ada", Age: 36); // named elements
Console.WriteLine(person.Name); // Ada
(string City, int Population) capital = ("Paris", 2_100_000);Tuples are value types and compare by value: (1, 2) == (1, 2) is true.
Returning several values
Tuples are the cleanest way to return more than one result:
(int Min, int Max) Range(int[] values)
{
int min = values[0], max = values[0];
foreach (var v in values)
{
if (v < min) min = v;
if (v > max) max = v;
}
return (min, max);
}
var r = Range(new[] { 4, 1, 9 });
Console.WriteLine($"{r.Min}–{r.Max}"); // 1–9Deconstruction
Unpack a tuple into separate variables:
var (min, max) = Range(new[] { 4, 1, 9 });
var (_, highest) = Range(new[] { 4, 1, 9 }); // _ discards a value you don't need
// The classic swap, without a temp variable:
int a = 1, b = 2;
(a, b) = (b, a);Records and KeyValuePair support deconstruction too: foreach (var (key, value) in dictionary).
Tuple or record?
Tuples are perfect for local, short-lived groupings — a method's private helper results, a quick pair in a LINQ query. When a group of values is passed around widely or deserves a name in your domain (Money, Address), declare a record instead.
Try it
C# runtime not loaded
Key takeaways
- Extension methods are static methods in static classes, with
thison the first parameter. - They let you call helpers as if they were instance methods — LINQ is built this way.
- Tuples group values without declaring a type:
(Name: "Ada", Age: 36). - Deconstruct with
var (a, b) = ...; use records when the grouping matters beyond one method.
Exercise
Write two string extension methods: Truncate(max) shortens text longer than max characters to its first max characters followed by "...", and IsPalindrome ignores case. Then make Stats return a named tuple with the Min, Max and Average of an array.
C# runtime not loaded
Hint 1
Truncate: text.Length <= max ? text : text[..max] + "..."
Hint 2
IsPalindrome: lower-case the text, then compare it with its reverse — new string(lower.Reverse().ToArray()).
Hint 3
Stats: return (numbers.Min(), numbers.Max(), numbers.Average()); — LINQ's Min, Max and Average are extension methods themselves!
Show solution
Console.WriteLine("Hello world".Truncate(5));
Console.WriteLine("Racecar".IsPalindrome());
var (min, max, average) = Stats(new[] { 4, 1, 7 });
Console.WriteLine($"{min} {max} {average}");
(int Min, int Max, double Average) Stats(int[] numbers) =>
(numbers.Min(), numbers.Max(), numbers.Average());
static class StringExtensions
{
public static string Truncate(this string text, int max) =>
text.Length <= max ? text : text[..max] + "...";
public static bool IsPalindrome(this string text)
{
string lower = text.ToLower();
return lower == new string(lower.Reverse().ToArray());
}
}Quiz
4 questions · 3 correct to pass1.What makes a method an extension method?
2.Can an extension method access private fields of the type it extends?
3.Given `var t = (Name: "Ada", Age: 36);`, how do you read the age?
4.What does `var (name, age) = person;` do for a tuple or record?
To complete this lesson
- ○ Solve the exercise
- ○ Pass the quiz (3/4 correct)