Partial types to fundamenty współpracy między twoim kodem a generowanym kodem! W 2015 roku znałeś partial classes z WinForms/WPF. W 2026 roku masz partial properties (C# 13), partial constructors i events (C# 14), i pełną integrację z Source Generators!
W tym wpisie poznasz partial classes (klasyka), partial methods, nowoczesne partial properties, partial constructors i events, i jak to wszystko współpracuje z Source Generators!
C# 14 (2026) - 🔥 Partial constructors i partial events!
Partial Classes - klasyka (C# 2.0)
Czym są partial classes?
🔍 Partial class = klasa podzielona na wiele plików
Partial = klasa może być zdefiniowana w wielu plikach
Kompilator łączy wszystkie części w jedną klasę
Idealne do separation of concerns: ręczny kod vs generowany kod
// File: Person.cs (ręczny kod)
public partial class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void Introduce()
{
Console.WriteLine($"Hi, I'm {Name}, {Age} years old");
}
}
// File: Person.Generated.cs (generowany kod)
public partial class Person
{
// Auto-generated validation
public bool Validate()
{
return !string.IsNullOrEmpty(Name) && Age > 0;
}
// Auto-generated serialization
public string ToJson()
{
return $"{{\"Name\":\"{Name}\",\"Age\":{Age}}}";
}
}
// Kompilator łączy obie części w jedną klasę!
var person = new Person { Name = "Jan", Age = 30 };
person.Introduce(); // Z Person.cs
string json = person.ToJson(); // Z Person.Generated.cs
bool valid = person.Validate(); // Z Person.Generated.cs
// Wszystko działa jak jedna klasa! ✨
🔍 Jak to działa? Kiedy ten kod się generuje?
1. KTO generuje kod?
Kod może być generowany przez:
Source Generator - narzędzie które działa PODCZAS kompilacji
Narzędzie zewnętrzne - np. T4 templates, generator CLI
IDE/Designer - np. WinForms designer generuje Form1.Designer.cs
Ty ręcznie - możesz sam stworzyć plik .Generated.cs
2. KIEDY się generuje?
Podczas kompilacji, NIE w runtime:
Twój kod (Person.cs)
↓
KOMPILACJA
↓
Source Generator → Tworzy Person.Generated.cs
↓
KOMPILATOR
↓
Łączy Person.cs + Person.Generated.cs → JEDEN typ Person
↓
.dll z kodem
✅ Person.cs - TWÓJ plik (generator NIGDY go nie dotyka!)
✅ Person.Generated.cs - generator może go NADPISAĆ bezpiecznie
✅ Twój kod jest bezpieczny - generator ma SWÓJ plik
✅ Nie ma ryzyka że generator zepsuje twój kod
5. TL;DR - Krótko
Piszesz Person.cs z partial class Person
Source Generator (podczas kompilacji) tworzy Person.Generated.cs
Kompilator łączy OBA w JEDNĄ klasę
Efekt: Masz metody które nie musiałeś pisać! ✨
To jak mieć asystenta który pisze boilerplate za Ciebie! 🤖
Partial classes - use cases
// Use case 1: Designer-generated code (WinForms, WPF)
// File: MainForm.cs (twój kod)
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent(); // Z MainForm.Designer.cs
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Clicked!");
}
}
// File: MainForm.Designer.cs (generowany przez designer)
public partial class MainForm
{
private Button button1;
private void InitializeComponent()
{
this.button1 = new Button();
// ... setup UI
}
}
// Separation: twój kod ↔ designer code
// Use case 2: Entity Framework DbContext
// File: MyDbContext.cs (twój kod)
public partial class MyDbContext : DbContext
{
// Twoje custom methods
public List<User> GetActiveUsers()
{
return Users.Where(u => u.IsActive).ToList();
}
}
// File: MyDbContext.Generated.cs (generowany przez EF)
public partial class MyDbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Generated configuration
}
}
// Use case 3: Multiple developers - separation
// File: Order.Business.cs (developer 1 - business logic)
public partial class Order
{
public decimal CalculateTotal()
{
return Items.Sum(i => i.Price * i.Quantity);
}
public bool CanBeCancelled()
{
return Status == OrderStatus.Pending;
}
}
// File: Order.Validation.cs (developer 2 - validation)
public partial class Order
{
public bool Validate()
{
return Items.Any() && CustomerId > 0;
}
public List<string> GetValidationErrors()
{
var errors = new List<string>();
if (!Items.Any()) errors.Add("Order must have items");
if (CustomerId <= 0) errors.Add("Invalid customer");
return errors;
}
}
Partial classes - zasady
// Zasady partial classes:
// 1. Wszystkie części muszą mieć keyword 'partial'
public partial class MyClass { } // ✅
public partial class MyClass { } // ✅
// 2. Wszystkie części muszą być w tym samym namespace
namespace MyApp
{
public partial class MyClass { } // ✅
}
namespace MyApp
{
public partial class MyClass { } // ✅
}
// 3. Wszystkie części muszą mieć ten sam access modifier
public partial class MyClass { } // ✅
public partial class MyClass { } // ✅
// internal partial class MyClass { } // ❌ BŁĄD - różne modifiers
// 4. Base class tylko w jednej części
public partial class MyClass : BaseClass { } // ✅
public partial class MyClass { } // ✅ OK - base tylko raz
// 5. Interfaces mogą być w wielu częściach
public partial class MyClass : IDisposable { }
public partial class MyClass : IComparable { } // ✅ OK
// Kompilator łączy: MyClass : IDisposable, IComparable
Partial Methods - deklaracja i implementacja
Partial methods - podstawy (C# 3.0)
// Partial method = deklaracja w jednej części, implementacja w innej (opcjonalna!)
// File: Person.cs
public partial class Person
{
public string Name { get; set; }
// Deklaracja partial method
partial void OnNameChanged();
public void SetName(string name)
{
Name = name;
OnNameChanged(); // Wywołanie partial method
}
}
// File: Person.Generated.cs
public partial class Person
{
// Implementacja partial method (opcjonalna!)
partial void OnNameChanged()
{
Console.WriteLine($"Name changed to: {Name}");
}
}
// Jeśli NIE ma implementacji, kompilator USUWA wywołanie!
// Zero overhead jeśli nie zaimplementowano! ✨
Partial methods - extended (C# 9+)
🎉 C# 9 - Extended partial methods
Partial methods mogą mieć return values, out parameters, i access modifiers!
// C# 9+ - partial methods z return value i modifiers
public partial class Calculator
{
// Deklaracja - MUSI być public/internal dla return values
public partial int Add(int a, int b);
// Deklaracja z out parameter
public partial bool TryParse(string input, out int result);
}
public partial class Calculator
{
// Implementacja - MUSI być zaimplementowana (bo ma return value)
public partial int Add(int a, int b)
{
return a + b;
}
// Implementacja z out
public partial bool TryParse(string input, out int result)
{
return int.TryParse(input, out result);
}
}
// Użycie
var calc = new Calculator();
int sum = calc.Add(5, 3); // 8
if (calc.TryParse("123", out int number))
{
Console.WriteLine(number); // 123
}
Partial methods - use cases
// Use case 1: Extensibility hooks w generowanym kodzie
// File: DataModel.Generated.cs (generated by tool)
public partial class DataModel
{
partial void OnLoaded();
partial void OnSaving();
partial void OnDeleting();
public void Load()
{
// Load data from DB
OnLoaded(); // Hook - może być zaimplementowany przez użytkownika
}
public void Save()
{
OnSaving(); // Hook
// Save to DB
}
}
// File: DataModel.cs (twój kod)
public partial class DataModel
{
// Implementujesz tylko potrzebne hooks
partial void OnLoaded()
{
Console.WriteLine("Data loaded!");
}
partial void OnSaving()
{
// Validation przed save
if (!Validate())
throw new InvalidOperationException();
}
// OnDeleting nie zaimplementowane - zostanie usunięte przez kompilator
}
// Use case 2: Source Generators
// File: Logger.cs (twój kod)
public partial class Logger
{
// Deklaracja - Source Generator wygeneruje implementację
[LoggerMessage(Level = LogLevel.Information, Message = "User {name} logged in")]
public partial void LogUserLogin(string name);
}
// File: Logger.Generated.cs (generated by Source Generator)
public partial class Logger
{
// Generated implementation
public partial void LogUserLogin(string name)
{
// Optimized logging code without boxing/allocations
_logger.Log(LogLevel.Information, $"User {name} logged in");
}
}
Feature
C# 3.0 Partial Methods
C# 9+ Extended Partial Methods
Return value
❌ Tylko void
✅ Dowolny typ
out/ref parameters
❌ Nie
✅ Tak
Access modifiers
❌ Zawsze private
✅ public, internal, etc.
Implementacja
Opcjonalna
Wymagana (jeśli return value)
Usunięcie gdy brak impl
✅ Tak (zero overhead)
❌ Nie (musi być impl)
🔥 Partial Properties (C# 13)
Problem przed C# 13
// Przed C# 13 - nie można mieć partial properties
public partial class Person
{
// Chcesz aby Source Generator wygenerował backing field i logic
// Ale nie można - properties nie mogą być partial!
// ❌ To nie działa w C# < 13:
// public partial string Name { get; set; }
// Musisz użyć partial methods jako workaround
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnNameChanged(); // Partial method hook
}
}
partial void OnNameChanged(); // Hook dla generatora
}
Partial Properties - C# 13!
🎉 NOWOŚĆ C# 13 - Partial Properties
Properties mogą być partial! Deklaracja w jednym pliku, implementacja w drugim!
// C# 13 - partial properties!
// File: Person.cs (twój kod)
public partial class Person
{
// Deklaracja partial property
public partial string Name { get; set; }
public partial int Age { get; set; }
}
// File: Person.Generated.cs (generated by Source Generator)
public partial class Person
{
// Implementacja partial property
private string _name;
public partial string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name)); // INotifyPropertyChanged
}
}
}
private int _age;
public partial int Age
{
get => _age;
set
{
if (_age != value)
{
_age = value;
OnPropertyChanged(nameof(Age));
}
}
}
// Generated INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// Użycie - wygląda jak normalna property!
var person = new Person();
person.PropertyChanged += (s, e) => Console.WriteLine($"{e.PropertyName} changed");
person.Name = "Jan"; // "Name changed"
person.Age = 30; // "Age changed"
Partial Properties - use cases
// Use case 1: INotifyPropertyChanged auto-generation
[GenerateNotifyPropertyChanged] // Custom attribute dla Source Generator
public partial class ViewModel
{
// Tylko deklaracja - Source Generator wygeneruje implementację
public partial string Title { get; set; }
public partial bool IsLoading { get; set; }
public partial int ItemCount { get; set; }
}
// Generated:
// - Backing fields
// - PropertyChanged event
// - OnPropertyChanged calls
// - INotifyPropertyChanged interface
// Use case 2: Validation
[GenerateValidation]
public partial class UserDto
{
[Required]
[MinLength(3)]
public partial string Name { get; set; }
[Range(18, 100)]
public partial int Age { get; set; }
[EmailAddress]
public partial string Email { get; set; }
}
// Generated:
// - Validation logic w setterach
// - IsValid property
// - GetValidationErrors() method
// Use case 3: Lazy loading
[GenerateLazy]
public partial class DataService
{
[Lazy]
public partial DbContext Database { get; }
[Lazy]
public partial HttpClient HttpClient { get; }
}
// Generated:
// - Lazy<T> backing fields
// - Gettery z lazy initialization
🔥 Partial Constructors i Events (C# 14)
Partial Constructors - C# 14
🎉 NOWOŚĆ C# 14 - Partial Constructors
Constructors mogą być partial! Część inicjalizacji w ręcznym kodzie, część w generowanym!
// C# 14 - partial constructors!
// File: Person.cs (twój kod)
public partial class Person
{
public partial Person(string name, int age); // Deklaracja
public string Name { get; }
public int Age { get; }
}
// File: Person.Generated.cs (generated)
public partial class Person
{
// Implementacja partial constructor
public partial Person(string name, int age)
{
Name = name;
Age = age;
// Generated initialization
_id = Guid.NewGuid();
_createdAt = DateTime.UtcNow;
// Generated validation
if (string.IsNullOrEmpty(name))
throw new ArgumentException(nameof(name));
if (age < 0)
throw new ArgumentException(nameof(age));
}
private Guid _id;
private DateTime _createdAt;
}
// Użycie
var person = new Person("Jan", 30);
// Konstruktor wykonuje kod z obu części!
Partial Events - C# 14
🎉 NOWOŚĆ C# 14 - Partial Events
Events mogą być partial! Deklaracja w jednym pliku, implementacja w drugim!
// C# 14 - partial events!
// File: OrderProcessor.cs (twój kod)
public partial class OrderProcessor
{
// Deklaracja partial event
public partial event EventHandler<OrderEventArgs> OrderProcessed;
public void ProcessOrder(Order order)
{
// Process order
// Raise event - implementacja w generated code
OrderProcessed?.Invoke(this, new OrderEventArgs { Order = order });
}
}
// File: OrderProcessor.Generated.cs (generated)
public partial class OrderProcessor
{
// Implementacja partial event z custom add/remove
private EventHandler<OrderEventArgs> _orderProcessedHandlers;
public partial event EventHandler<OrderEventArgs> OrderProcessed
{
add
{
_orderProcessedHandlers += value;
// Generated logging
Console.WriteLine($"Handler added: {value.Method.Name}");
}
remove
{
_orderProcessedHandlers -= value;
// Generated logging
Console.WriteLine($"Handler removed: {value.Method.Name}");
}
}
}
// Użycie
var processor = new OrderProcessor();
processor.OrderProcessed += OnOrderProcessed; // "Handler added: OnOrderProcessed"
processor.ProcessOrder(order);
processor.OrderProcessed -= OnOrderProcessed; // "Handler removed: OnOrderProcessed"
Praktyczne przykłady - C# 14 partial members
// Przykład 1: Dependency Injection z partial constructors
[GenerateDI]
public partial class UserService
{
// Deklaracja - Source Generator wygeneruje implementację
public partial UserService(ILogger logger, IRepository repository);
private ILogger _logger;
private IRepository _repository;
}
// Generated:
public partial class UserService
{
public partial UserService(ILogger logger, IRepository repository)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
// Generated logging
_logger.LogInformation("UserService created");
}
}
// Przykład 2: Event sourcing z partial events
[GenerateEventSourcing]
public partial class Account
{
public partial event EventHandler<MoneyDepositedEvent> MoneyDeposited;
public partial event EventHandler<MoneyWithdrawnEvent> MoneyWithdrawn;
public void Deposit(decimal amount)
{
Balance += amount;
MoneyDeposited?.Invoke(this, new MoneyDepositedEvent(amount));
}
}
// Generated:
public partial class Account
{
private List<object> _events = new();
public partial event EventHandler<MoneyDepositedEvent> MoneyDeposited
{
add
{
_moneyDepositedHandlers += value;
_events.Add(new EventSubscribed("MoneyDeposited"));
}
remove
{
_moneyDepositedHandlers -= value;
_events.Add(new EventUnsubscribed("MoneyDeposited"));
}
}
public IReadOnlyList<object> GetEvents() => _events;
}
Source Generators i Partial Types
Source Generators używają partial!
// Source Generators ZAWSZE generują partial classes/methods/properties!
// Twój kod - oznaczasz co chcesz wygenerować
[JsonSerializable(typeof(Person))]
public partial class PersonContext : JsonSerializerContext { }
// Source Generator generuje partial class
public partial class PersonContext
{
// Generated serialization code
// ...
}
// Przykład: Custom Source Generator
// Attribute do oznaczenia klasy
[AttributeUsage(AttributeTargets.Class)]
public class AutoToStringAttribute : Attribute { }
// Twój kod
[AutoToString]
public partial class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// Source Generator generuje
public partial class Person
{
public override string ToString()
{
return $"Person {{ Name = {Name}, Age = {Age} }}";
}
}
Workflow: Ty + Source Generator
// Workflow z Source Generators:
// 1. Piszesz partial deklarację
// 2. Dodajesz attributes/markers
// 3. Source Generator generuje implementację
// 4. Kompilator łączy w jedną klasę
// Przykład: Repository pattern
// File: UserRepository.cs (twój kod)
[GenerateRepository]
public partial class UserRepository
{
// Tylko deklaracje - Source Generator wygeneruje implementację
public partial Task<User> GetByIdAsync(int id);
public partial Task<List<User>> GetAllAsync();
public partial Task SaveAsync(User user);
public partial Task DeleteAsync(int id);
}
// File: UserRepository.Generated.cs (generated by Source Generator)
public partial class UserRepository
{
private readonly DbContext _context;
// Generated constructor
public UserRepository(DbContext context)
{
_context = context;
}
// Generated implementations
public partial async Task<User> GetByIdAsync(int id)
{
return await _context.Users.FindAsync(id);
}
public partial async Task<List<User>> GetAllAsync()
{
return await _context.Users.ToListAsync();
}
public partial async Task SaveAsync(User user)
{
_context.Users.Update(user);
await _context.SaveChangesAsync();
}
public partial async Task DeleteAsync(int id)
{
var user = await GetByIdAsync(id);
if (user != null)
{
_context.Users.Remove(user);
await _context.SaveChangesAsync();
}
}
}
// Użycie - wygląda jak normalna klasa!
var repository = new UserRepository(dbContext);
var user = await repository.GetByIdAsync(123);
Best practices - partial types z Source Generators
// Best practice 1: Nazwij generated file z suffixem .Generated.cs
// Person.cs - twój kod
// Person.Generated.cs - generated code
// Best practice 2: Dodaj comment header w generated files
// <auto-generated />
// This file is auto-generated by MySourceGenerator
// Do not modify this file manually
// Best practice 3: Używaj partial dla wszystkiego co będzie generowane
public partial class MyClass { } // ✅
public partial void MyMethod(); // ✅
public partial string MyProperty { get; } // ✅ (C# 13+)
// Best practice 4: Dokumentuj partial deklaracje
/// <summary>
/// Implementation generated by Source Generator
/// </summary>
public partial void GeneratedMethod();
// Best practice 5: Nie mieszaj generated i manual code w jednym pliku
// Person.cs - TYLKO twój kod
// Person.Generated.cs - TYLKO generated code
// ❌ Nie rób tego:
// Person.cs
public partial class Person
{
// Twój kod
public void MyMethod() { }
// Generated code - NIE!
// Powinno być w Person.Generated.cs!
}
Podsumowanie
Partial types - współpraca z generowanym kodem!
✅ Partial classes (C# 2.0) - klasa w wielu plikach, separation of concerns
✅ 🔥 Partial properties (C# 13) - properties mogą być partial!
✅ 🔥 Partial constructors (C# 14) - constructors mogą być partial!
✅ 🔥 Partial events (C# 14) - events mogą być partial!
✅ Source Generators - używają partial do code generation
✅ Best practices - naming conventions, separation, documentation
W kolejnym wpisie skupimy się na File-scoped types oraz Global Usings!
Zadanie dla Ciebie 🎯
Stwórz system używający partial types dla separation of concerns:
// File: Product.cs (business logic)
public partial class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
public decimal CalculateDiscount(decimal percentage)
{
return Price * (1 - percentage / 100);
}
}
// File: Product.Validation.cs (validation logic)
public partial class Product
{
partial void OnValidate();
public bool Validate()
{
OnValidate(); // Hook dla extensions
return !string.IsNullOrEmpty(Name) && Price > 0 && Stock >= 0;
}
public List<string> GetValidationErrors()
{
var errors = new List<string>();
if (string.IsNullOrEmpty(Name)) errors.Add("Name is required");
if (Price <= 0) errors.Add("Price must be positive");
if (Stock < 0) errors.Add("Stock cannot be negative");
return errors;
}
}
// File: Product.Persistence.cs (database logic)
public partial class Product
{
public void Save(DbContext db)
{
if (!Validate())
throw new InvalidOperationException("Invalid product");
db.Products.Update(this);
db.SaveChanges();
}
public static Product Load(DbContext db, int id)
{
return db.Products.Find(id);
}
}
Wymagania:
3+ pliki dla tej samej klasy (business, validation, persistence)
Partial methods jako hooks między częściami
Clear separation of concerns
Każdy plik ma jasną odpowiedzialność
🎯 BONUS: Simple Source Generator dla INotifyPropertyChanged
Stwórz conceptual design dla Source Generator który generuje INotifyPropertyChanged!
Projekt (design, nie implementacja):
Attribute do oznaczenia:
[GenerateNotifyPropertyChanged] na klasie
[Notify] na properties które mają triggerować event
Twój kod (input):
[GenerateNotifyPropertyChanged]
public partial class Person
{
[Notify]
public partial string Name { get; set; }
[Notify]
public partial int Age { get; set; }
// This property won't trigger events
public string InternalId { get; set; }
}
Generated code (output):
public partial class Person : INotifyPropertyChanged
{
private string _name;
private int _age;
public partial string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public partial int Age
{
get => _age;
set
{
if (_age != value)
{
_age = value;
OnPropertyChanged(nameof(Age));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
}
Features:
Automatyczne backing fields
PropertyChanged event implementation
Value equality check przed raise
INotifyPropertyChanged interface
Użycie:
var person = new Person();
// Subscribe to changes
person.PropertyChanged += (s, e) =>
{
Console.WriteLine($"Property {e.PropertyName} changed!");
};
person.Name = "Jan"; // "Property Name changed!"
person.Age = 30; // "Property Age changed!"
person.InternalId = "123"; // No event - not marked with [Notify]
Ten projekt pokazuje jak Source Generators używają partial types do generowania boilerplate code! 🚀⚙️