Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Udostępnij Udostępnij Kontakt
Wprowadzenie

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!

📅 Timeline - ewolucja partial types
  • C# 2.0 (2005) - 🔥 Partial classes - WinForms/WPF designer
  • C# 3.0 (2007) - Partial methods (declaration + implementation)
  • C# 9.0 (2020) - Partial methods z return values, out parameters
  • C# 9.0 (2020) - Source Generators używają partial classes
  • C# 13 (2025) - 🔥 Partial properties!
  • 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

3. Jak wygląda w Visual Studio?

📁 MyProject
  📄 Person.cs           ← Twój plik (piszesz ręcznie)
  📄 Person.Generated.cs ← Wygenerowany (auto-created)

Ale dla kompilatora to JEDNA klasa Person!

4. Dlaczego partial?

Separacja i bezpieczeństwo:

  • 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

  1. Piszesz Person.cs z partial class Person
  2. Source Generator (podczas kompilacji) tworzy Person.Generated.cs
  3. Kompilator łączy OBA w JEDNĄ klasę
  4. 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 methods (C# 3.0) - deklaracja + opcjonalna implementacja
  • Extended partial methods (C# 9) - return values, out parameters, modifiers
  • 🔥 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:

  1. 3+ pliki dla tej samej klasy (business, validation, persistence)
  2. Partial methods jako hooks między częściami
  3. Clear separation of concerns
  4. 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):

  1. Attribute do oznaczenia:
    • [GenerateNotifyPropertyChanged] na klasie
    • [Notify] na properties które mają triggerować event
  2. 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; }
    }
  3. 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));
        }
    }
  4. 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! 🚀⚙️