C#
Completed
-
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
27: Partial Classes and Methods
Imagine you're designing a house. You have a structural engineer who handles the "bones"—the load-bearing walls, the plumbing, and the electrical wiring. Then you have an interior designer who handles the "feel"—the paint colors, the furniture layout, and the lighting fixtures. They aren't building two different houses; they're both working on the exact same house. But for the sake of sanity, they each have their own set of blueprints. If the structural engineer needs to move a pipe, they don't want to accidentally smudge the designer's carefully chosen wallpaper swatch.
That is exactly what partial classes do in C#. They allow you to split a single class definition across multiple files. When the compiler runs, it gathers all those pieces and stitches them together into one single class. You'll most often see this when you're using tools that auto-generate code. I've spent plenty of hours fighting with generated code in the past, and partial classes are the only reason we don't lose our minds when a tool decides to "refresh" a file and overwrite everything we manually wrote.
Splitting the logic across files
Let's say you have a UserAccount class. In a real-world app, a tool might generate the properties based on your database schema. You don't want to touch that file because if you change the database and regenerate the class, your manual changes vanish. Instead, you mark the class as partial.
// File: UserAccount.Generated.cs
// This file is managed by a tool. Don't touch it!
public partial class UserAccount
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
}
Now, you can create a second file for your actual business logic. Since it's also marked partial, the compiler knows it belongs to the same class.
// File: UserAccount.Logic.cs
// This is where we put the stuff we actually care about.
public partial class UserAccount
{
public bool IsValidEmail()
{
return Email.Contains("@") && Email.Contains(".");
}
public string GetDisplayName()
{
return $"{Username} ({Email})";
}
}
To the rest of your program, UserAccount is just one class with five members. It doesn't know (or care) that the code is physically located in two different files.
Creating hooks with partial methods
Partial methods are a slightly more specialized tool. Think of them as "hooks." They allow the generated part of a class to say, "I'm going to do something here, and if the human wants to add extra logic at this specific moment, they can. If they don't, just ignore this call."
Here is how that looks in practice. The generated code defines the method signature but provides no body.
// File: UserAccount.Generated.cs
public partial class UserAccount
{
// This is the declaration. It's like a placeholder.
partial void OnEmailChanged();
public void UpdateEmail(string newEmail)
{
this.Email = newEmail;
// We call the hook here.
OnEmailChanged();
}
}
Now, in your logic file, you can choose whether or not to implement that method. If you don't write the implementation, the C# compiler is smart enough to completely remove the call to OnEmailChanged() from the final binary. There's zero performance hit for a hook you didn't use.
// File: UserAccount.Logic.cs
public partial class UserAccount
{
// We decided we actually want to do something when the email changes.
partial void OnEmailChanged()
{
Console.WriteLine($"Log: Email for user {Username} was updated.");
}
}
I personally find this pattern incredibly useful for logging or validation without cluttering the "core" generated logic of a system. Just remember: partial methods must return void and cannot have access modifiers (like public or private) if they are to be omitted by the compiler when not implemented.
📋 Practical Task
Building a Partial Payment Processor
You are building a payment system where the API communication code is generated by a third-party library, but the logging and validation logic must be written by you. Your goal is to separate these concerns using partial classes and methods.
- Create a partial class named
PaymentProcessor. - In the "generated" portion of the class, create a method
ProcessPayment(decimal amount). Inside this method, call a partial method namedOnPaymentProcessedbefore printing "Payment of {amount} sent to API" to the console. - In the "logic" portion of the class (the part you are implementing), provide the implementation for
OnPaymentProcessed. This method should print "Log: Transaction recorded in local database" to the console. - In your
Mainmethod, instantiate thePaymentProcessorand callProcessPayment(99.99m).
Verify that both the log message and the API message appear in the console, proving that the two partial pieces of the class are working as one.
There are no comments for now.