Introduction
Maintaining clean and scalable code is very important, especially in big projects or when many developers are working together. One golden rule we always hear is “separation of concerns.” To follow that, many of us use partial classes to split different responsibilities nicely.
But until C# 12, there was one irritating limitation - we couldn’t make properties or indexers partial. Because of that, sometimes we had to mix up different logics in one place, which made the code messy. It was frustrating, especially when working with tools like Entity Framework, code generators, etc.
Now in C# 13.0, this small but powerful feature has come with Partial Properties and Indexers. It may look like a small update, but in real development, it’s actually a big one.
In this blog, let’s see how we were managing earlier, what’s new in C# 13, and how this feature can help us keep our code clean, readable, and more maintainable.
How was it earlier?
Let's say you're working with a code generator (like EF Core) that creates a class like this.
public partial class Product
{
public string Name { get; set; }
}
Now, suppose you want to add some custom logic, like trimming extra spaces or checking that the name is not empty or null. But since the Name property is already there in the generated code, your can't do anything. You’re left with only a few not-so-nice options.
- Shadow property: You make a new property with a similar name and manually sync values. A bit messy.
- Use backing fields: override the generated class and handle it yourself, but then you might break the generated code. Risky.
- Try partial methods or extension methods. They can work, but they feel like a jugaad (hack), not clean or natural.
End result? Code becomes untidy, logic is scattered in multiple files, and worst part — some developers stop following best practices just because tools don’t support it well.
How can we resolve this in C# 13.0?
In C# 13.0, we have a new feature called partial properties and partial indexers. This allows you to declare a property in one part of a partial class and implement it in another.
This update makes property logic more like how we write methods — now we can do things like this.
// Part 1
public partial void DoSomething();
// Part 2
public partial void DoSomething()
{
// logic
}
We can now do the same with properties.
// Part 1
public partial string Name { get; set; }
// Part 2
public partial string Name
{
get => _name;
set => _name = value.Trim();
}
Simple and powerful! Now let's see a real-life example where we can apply this.
Real-World example - Email validation in a generated model
Let’s say we have a model generated by EF Core or a source generator.
// File: Customer.Generated.cs
public partial class Customer
{
public partial string Email { get; set; }
}
Above is the auto-generated code, where we can't do much. Hence, creating another partial class and extending it as below.
// File: Customer.cs
public partial class Customer
{
public partial string Email
{
get => _email;
set
{
if (!value.Contains("@"))
throw new ArgumentException("Invalid email address.");
_email = value.Trim().ToLower();
}
}
private string _email;
}
This is now clean, separated, and maintainable.
How does it work with Collections?
Earlier, indexers also had the same restriction. Now with partial indexers, working with custom data structures becomes much easier. Let’s see how.
// File: Matrix.Generated.cs
public partial class Matrix
{
public partial double this[int row, int col] { get; set; }
}
// File: Matrix.cs
public partial class Matrix
{
public partial double this[int row, int col]
{
get => _data[row, col];
set => _data[row, col] = value;
}
private double[,] _data = new double[3, 3];
}
Now your indexer logic can be maintained cleanly alongside internal data handling.
Benefits we are getting out of it
So, what do we really get with these new Partial Properties and Indexers?
- Cleaner Code Separation: Now your business logic stays in your own file, and the generated code stays untouched. No more mixing things up.
- Better Tooling Support: Works smoothly with Entity Framework, Blazor, source generators, and even your own scaffolding tools if you are using.
- No More Jugaad (Hacks): No need to hide properties or write duplicate logic. Just write behavior where it actually belongs.
- Team Work Becomes Easy: The UI team can handle things like trimming or formatting, backend team can focus on saving data. Clean division.
- Easier to Maintain: Just open the file, and at a glance, you know what’s written by you and what’s coming from code generation.