27. Extension Methods in C#
Level: Intermediate
Goal: Learn to add methods to existing types (strings, lists) without changing their source code.
- What extension methods are
- Why the this keyword is used
- Create an extension method for string
- Use extension methods in school examples
- Avoid common extension method mistakes
Extension methods let you add new abilities to types without touching their code.
Example: Add a method to string type that checks if it is a valid email.
string email = "Sahasra@school.com";
bool valid = email.IsValidEmail(); // Method we added
Extension Methods Reference
| Concept | Purpose | Syntax | Use When |
|---|---|---|---|
| Extension method | Add method to type | public static bool Method(this Type t) | Need to extend type without modifying it |
this keyword | Marks first param as "this" type | public static ... Method(this Type t, ...) | Required for extension method syntax |
| Static class | Container for extensions | public static class TypeExtensions | Namespace for related extensions |
| Chain extensions | Call multiple extensions | obj.Method1().Method2().Method3() | Build fluent, readable chains |
| Extend sealed | Extend sealed/built-in types | public static class StringExt on string | Can't inherit sealed types, so extend |
| Intellisense | IDE shows as regular method | string s = "Sahasra"; s.IsValid() | Extensions appear in autocomplete |
Quick Definitions
- Extension method - Add method to existing class without inheritance
thiskeyword - First parameter: class being extended- Fluent API - Chain method calls (method returns object itself)
- Reusability - Extend standard types like string, int, List
- Namespace - Must be in scope to use extension method
- Static class - Required container for extension methods
- Chaining - Call multiple methods one after another
- Built-in types - string, int, List, Dictionary, etc.
- Syntactic sugar - Appears as if added to original class
- LINQ - Uses extension methods extensively (Where, Select, OrderBy)
Syntax
// Must be: static class, static method, first param = this TypeToExtend
public static class StringExtensions
{
public static bool IsValidEmail(this string email)
=> email.Contains("@") && email.Contains(".");
public static string ToTitleCase(this string text)
=> System.Globalization.CultureInfo.CurrentCulture
.TextInfo.ToTitleCase(text.ToLower());
public static string Truncate(this string text, int maxLength)
=> text.Length <= maxLength ? text : text[..maxLength] + "...";
}
// Now string has these methods
string email = "Sahasra@school.com";
Console.WriteLine(email.IsValidEmail()); // True
string name = "Sahasra kumar";
Console.WriteLine(name.ToTitleCase()); // Sahasra Kumar
Student Extensions
public static class StudentExtensions
{
public static bool HasPassed(this Student s)
=> s.Percentage >= 35;
public static bool IsDistinction(this Student s)
=> s.Percentage >= 75;
public static string GetGrade(this Student s) => s.Percentage switch
{
>= 90 => "A+", >= 80 => "A", >= 70 => "B",
>= 60 => "C", >= 35 => "D", _ => "F"
};
public static bool IsFeePending(this Student s)
=> s.OutstandingFees > 0;
public static string GetStatus(this Student s)
{
if (!s.HasPassed()) return "Fail";
if (s.IsFeePending()) return "Pass (Fees Pending)";
if (s.IsDistinction()) return "Pass with Distinction";
return "Pass";
}
}
// Collection extensions
public static class StudentListExtensions
{
public static IEnumerable<Student> Passed(this IEnumerable<Student> students)
=> students.Where(s => s.HasPassed());
public static IEnumerable<Student> Failed(this IEnumerable<Student> students)
=> students.Where(s => !s.HasPassed());
public static IEnumerable<Student> WithFeesPending(this IEnumerable<Student> students)
=> students.Where(s => s.IsFeePending());
public static IEnumerable<Student> InClass(this IEnumerable<Student> students, string cls)
=> students.Where(s => s.ClassName == cls);
public static double ClassAverage(this IEnumerable<Student> students)
=> students.Any() ? students.Average(s => s.Percentage) : 0;
public static Student? Topper(this IEnumerable<Student> students)
=> students.OrderByDescending(s => s.Percentage).FirstOrDefault();
}
Usage - Reads Like English
When You'll Use This in SMS
Extension methods simplify SMS code.
Extend Student:
student.HasPassed()
student.IsDistinction()
student.GetGrade()
student.IsFeePending()
student.GetStatus()
Extend Student lists:
students.Passed()
students.Failed()
students.InClass("10-A")
students.HighestScorer()
students.ClassAverage()
Extend string for validation:
email.IsValidEmail()
phone.IsValidPhone()
name.IsValidStudentName()
Real impact: Extension methods organize SMS logic. Student class stays clean, validation in separate extensions. Fluent chain: students.InClass("10-A").Passed().OrderByGrade().
Try This Now
- Create StringExtensions: IsValidEmail, Truncate
- Create StudentExtensions: HasPassed, GetGrade, GetStatus
- Chain methods:
student.GetGrade() + " " + student.GetStatus() - Extend
IEnumerable<Student>for filtering - Test with student data
Extension methods explained: syntax, fluent APIs, extending built-in types, LINQ extensions. Video coming soon. Subscribe to NexCoding YouTube for updates.
Common Mistakes
? Forgetting static or this keyword:
// Missing static
public class StringExtensions
{
public bool IsValidEmail(this string email) { } // ERROR!
}
// Missing this
public static class StringExtensions
{
public static bool IsValidEmail(string email) { } // Not an extension!
}
? Both required:
public static class StringExtensions
{
public static bool IsValidEmail(this string email) { } // Correct
}
? Extension method in wrong namespace (IDE won't show it):
// In MyProject.Utilities namespace
public static class StudentExt { ... }
// In MyProject.Models - extension NOT visible unless using MyProject.Utilities
var s = new Student();
s.HasPassed(); // Won't appear in autocomplete!
? Add using or put in common namespace:
using MyProject.Utilities; // Now StudentExt methods visible
var s = new Student();
s.HasPassed(); // Works!
? Shadowing built-in methods (confuses developers):
// BAD - creates confusion
public static class ListExt
{
public static void Add(this List<int> items, int value)
{
// Custom logic
}
}
? Use different names:
public static void AddIfNew(this List<int> items, int value)
{
if (!items.Contains(value))
items.Add(value); // Clear intent
}
? Making extension too broad:
public static void Process(this object obj) // Every object has this?
{
// ...
}
? Extend specific type:
public static void Process(this Student student) // Clear who needs it
{
// ...
}
Best Practices
- Put in static class named
TypeExtensions- Follows C# convention - Place in appropriate namespace - Likely
YourProject.Extensions - Add
using YourProject.Extensions- So IDE shows extensions - Don't shadow built-in methods - Create new names (AddIfNew, not Add)
- Use for domain-specific logic -
student.HasPassed()not generic operations - Extend collections appropriately - Extend
IEnumerable<T>for reusability - Keep extensions focused - One concern per static class
- Document extension methods - XML comments for IDE tooltip help
- Avoid extension methods on
object- Too broad, pollutes all types - Consider performance - Extensions do the same thing as regular methods (no overhead)
Extension method adds method to existing type WITHOUT modifying type or inheriting it.
// Add IsValidEmail to string without changing string class
public static class StringExt
{
public static bool IsValidEmail(this string email)
=> email.Contains("@") && email.Contains(".");
}
string e = "Sahasra@school.com";
e.IsValidEmail(); // Looks like regular method!
Why? Can't modify sealed types (string, int, List). Can't inherit string. Extension adds functionality cleanly.
Three requirements: (1) static class, (2) static method, (3) first param with this.
public static class StudentExt
{
public static bool HasPassed(this Student student)
=> student.Percentage >= 35;
}
// Compiler transforms this:
var s = new Student();
s.HasPassed();
// To this:
StudentExt.HasPassed(s);
this keyword tells compiler: "this first param is the object being extended."
Yes! That's the whole point. String is sealed (can't inherit), so extension methods let you add methods:
public static class StringExt
{
public static string Truncate(this string text, int len)
=> text.Length > len ? text.Substring(0, len) + "..." : text;
}
"Hello World".Truncate(5); // Hello...
Works for string, int, List, DateTime - any type. Even your own sealed classes.
Put in static class TypeExtensions in YourProject.Extensions namespace:
// StudentExtensions.cs
namespace SchoolApp.Extensions
{
public static class StudentExtensions
{
public static bool HasPassed(this Student s) => s.Percentage >= 35;
}
}
// Program.cs - must add using!
using SchoolApp.Extensions;
var s = new Student();
s.HasPassed(); // Now appears in autocomplete
Without using, IDE won't show extension methods. Add using = IDE loads extensions = autocomplete works.
Extension methods on IEnumerable<T> chain together:
// Each returns IEnumerable<Student>, can chain next extension
students
.InClass("10th") // IEnumerable<Student>
.Passed() // IEnumerable<Student>
.OrderByDescending(s => s.Percentage) // IEnumerable<Student>
.First();
This is how LINQ works - Where, Select, OrderBy are extension methods on IEnumerable.
Chainable because each returns collection, not single value.
Don't extend object (pollutes every type). Don't shadow built-in methods (confuses). Don't use when regular method or inheritance would be clearer.
// BAD - too general
public static void Process(this object obj) { }
// BAD - shadows List.Add
public static void Add(this List<int> items, int x) { }
// GOOD - specific, clear intent
public static bool HasPassed(this Student s) => s.Percentage >= 35;
Extensions best for domain-specific operations on types you can't modify.
Use ChatGPT, Claude, or Copilot to go deeper on C# extension methods. Try these prompts:
"What are extension methods in C# and when should I use them over regular methods?""Can I add extension methods to sealed classes or built-in types like string?""Show me how LINQ methods like Where and Select are actually extension methods""What are the limitations of extension methods in C#?"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.