-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
65: Practice Exercise: Building a Fluent Extension Method Library
How do I actually make my extension methods "chainable"?
The secret to a fluent interface is surprisingly simple: your method must return the object it's operating on. If you're extending a class called QueryBuilder, your extension method's return type must be QueryBuilder, and the last line of your logic should almost always be return builder;.
I'll show you what I mean. Let's say we're building a simple SQL-like query generator. Instead of having a bunch of separate calls, we want to chain them together. Notice how each method takes the QueryBuilder as the this parameter and returns it right back at the end.
public static class QueryExtensions
{
public static QueryBuilder Where(this QueryBuilder builder, string column, string value)
{
builder.Filters.Add($"{column} = '{value}'");
return builder; // This is the magic line
}
public static QueryBuilder OrderBy(this QueryBuilder builder, string column)
{
builder.SortColumn = column;
return builder;
}
}
// Now you can do this:
var query = new QueryBuilder()
.Where("Status", "Active")
.Where("Role", "Admin")
.OrderBy("CreatedAt");
Should I be modifying the original object or returning a new copy?
This is where things get interesting. It depends on whether you're working with a mutable object (like a custom class) or an immutable one (like a string). If you're extending string, you can't "modify" it—you have to return a new string. This is still "fluent," but it's a different memory pattern.
I generally recommend sticking to mutation for "Builder" patterns because it's more performant. But if you're building a data transformation pipeline, go for immutability. If I were writing a string-cleanup library for a CSV importer, it would look like this:
public static class StringCleaningExtensions
{
public static string RemoveWhitespace(this string text)
=> text?.Replace(" ", "") ?? string.Empty;
public static string ToUpperCase(this string text)
=> text?.ToUpper() ?? string.Empty;
public static string EnsureTrailingComma(this string text)
=> text.EndsWith(",") ? text : text + ",";
}
// Usage:
string cleanData = " John Doe ".RemoveWhitespace().ToUpperCase().EnsureTrailingComma();
// Result: "JOHNDOE,"
Won't these methods clutter my IntelliSense everywhere?
Yes, they will—if you put them in a generic namespace like System or your root project namespace. I've seen projects where the string type had 50+ extension methods attached to it, and it became a nightmare to find the built-in .NET methods.
The professional way to handle this is to put your fluent extensions in a dedicated namespace, something like YourProject.Extensions.Fluent. That way, the methods only appear in the autocomplete list when a developer explicitly adds using YourProject.Extensions.Fluent; at the top of their file. It keeps the global scope clean and makes it clear to other engineers that they are opting into a specific "fluent" style of coding.
📋 Practical Task
Exercise: Build a Fluent Email Message Composer
Your task is to create a fluent extension library for a simple EmailMessage class. This will allow a developer to "compose" an email using a chained syntax.
Requirements:
- Create a class
EmailMessagewith properties forTo,Subject,Body, and a list ofAttachments. - Create a static class
EmailExtensionscontaining the following extension methods:SetRecipient(this EmailMessage email, string recipient)SetSubject(this EmailMessage email, string subject)SetBody(this EmailMessage email, string body)AddAttachment(this EmailMessage email, string fileName)
- Ensure every method returns the
EmailMessageinstance to allow chaining. - In your
Mainmethod, instantiate anEmailMessageand use a single statement (chaining) to set the recipient to "boss@company.com", the subject to "Quarterly Report", the body to "Please find it attached", and add two files: "report.pdf" and "data.xlsx".
There are no comments for now.