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

Atrybuty i Refleksja to fundamenty metaprogramowania w C#. W 2015 roku używałeś podstawowych atrybutów i refleksji do introspekcji w czasie wykonywania. W 2026 roku masz Source Generators (generatory źródła) jako alternatywę w czasie kompilacji!

W tym wpisie poznasz wbudowane atrybuty (Obsolete, Conditional, CallerMemberName), nauczymy się tworzyć własne atrybuty, poznamy podstawy refleksji, zrobimy wprowadzenie do Source Generators!

📅 Timeline - ewolucja attributes i reflection
  • C# 1.0 (2002) - Attributes, Reflection basics
  • C# 5.0 (2012) - Caller info attributes (CallerMemberName, etc.)
  • C# 7.3 (2018) - Generic attributes constraints
  • C# 9.0 (2020) - 🔥 Source Generators introduced!
  • C# 11 (2022) - Generic attributes
  • C# 12+ (2023+) - Improved Source Generators, incremental generators
Czym są Attributes?

Attributes - metadane w kodzie

🔍 Attribute = metadane o kodzie

Attribute = specjalny typ który dodaje metadane do elementów kodu (klasy, metody, properties, etc.)
Metadane mogą być czytane w runtime przez Reflection lub w compile-time przez kompilator/Source Generators

// Attribute to klasa dziedzicząca po System.Attribute
// Używasz atrybutów przez [AttributeName]

// Przykład - [Obsolete] attribute
[Obsolete("Ta metoda jest przestarzała, użyj NewMethod()")]
public void OldMethod()
{
    Console.WriteLine("Old implementation");
}

// Kompilator pokaże warning gdy ktoś użyje OldMethod!
OldMethod();  // ⚠️ CS0618: 'OldMethod' is obsolete: 'Ta metoda jest przestarzała...'

// Attributes mogą mieć parametry
[Obsolete("Użyj NewMethod()", error: true)]  // error: true = błąd kompilacji!
public void VeryOldMethod()
{
    Console.WriteLine("Very old");
}

// VeryOldMethod();  // ❌ BŁĄD kompilacji!

Gdzie można używać attributes?

// Attributes można dodać do prawie wszystkiego!

// Class
[Serializable]
public class Person { }

// Method
[Obsolete]
public void DoSomething() { }

// Property
[Required]
public string Name { get; set; }

// Field
[NonSerialized]
private int _tempData;

// Parameter
public void Log([CallerMemberName] string caller = "") { }

// Return value
[return: MarshalAs(UnmanagedType.Bool)]
public bool CheckStatus() { return true; }

// Assembly (w osobnym pliku, zwykle AssemblyInfo.cs)
[assembly: AssemblyVersion("1.0.0.0")]

// Module
[module: SomeAttribute]
Built-in Attributes - najważniejsze

[Obsolete] - deprecation warnings

// [Obsolete] - oznacz kod jako przestarzały
[Obsolete("Użyj GetUserAsync() zamiast tego")]
public User GetUser(int id)
{
    return _repository.GetUser(id);
}

// Warning w kompilacji
var user = GetUser(123);  // ⚠️ Warning

// [Obsolete] z error: true
[Obsolete("Ta metoda została usunięta w v2.0", error: true)]
public void RemovedMethod()
{
    // ...
}

// RemovedMethod();  // ❌ BŁĄD kompilacji

// Praktyczne użycie - migration path
public class UserService
{
    // Nowa metoda
    public async Task<User> GetUserAsync(int id)
    {
        return await _repository.GetUserAsync(id);
    }
    
    // Stara metoda - deprecated
    [Obsolete("Użyj GetUserAsync() dla lepszej wydajności")]
    public User GetUser(int id)
    {
        return GetUserAsync(id).GetAwaiter().GetResult();
    }
}

[Conditional] - conditional compilation

// [Conditional] - metoda jest wywoływana TYLKO gdy symbol jest zdefiniowany
using System.Diagnostics;

public class Logger
{
    [Conditional("DEBUG")]
    public static void LogDebug(string message)
    {
        Console.WriteLine($"[DEBUG] {message}");
    }
    
    [Conditional("TRACE")]
    public static void LogTrace(string message)
    {
        Console.WriteLine($"[TRACE] {message}");
    }
}

// W Debug build (DEBUG symbol defined)
Logger.LogDebug("Starting process");  // ✅ Wywoła metodę

// W Release build (DEBUG symbol NOT defined)
Logger.LogDebug("Starting process");  // ❌ Kod jest USUNIĘTY przez kompilator!

// Praktyczne użycie - performance logging
public class PerformanceMonitor
{
    [Conditional("PERFORMANCE_LOGGING")]
    public static void LogPerformance(string operation, long milliseconds)
    {
        Console.WriteLine($"{operation} took {milliseconds}ms");
    }
}

// W production build - zero overhead (kod usunięty)!
PerformanceMonitor.LogPerformance("Database query", 150);

Caller Info Attributes - metadane o callerze

// Caller info attributes - kompilator automatycznie wypełnia parametry!
using System.Runtime.CompilerServices;

public class Logger
{
    public static void Log(
        string message,
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string filePath = "",
        [CallerLineNumber] int lineNumber = 0)
    {
        Console.WriteLine($"[{memberName}] {message}");
        Console.WriteLine($"  at {filePath}:{lineNumber}");
    }
}

// Użycie - nie podajesz parametrów caller info!
public class UserService
{
    public void CreateUser(string name)
    {
        Logger.Log("Creating user");
        // Output:
        // [CreateUser] Creating user
        //   at C:\Project\UserService.cs:15
        
        // Kompilator AUTOMATYCZNIE wypełnił:
        // memberName = "CreateUser"
        // filePath = pełna ścieżka do pliku
        // lineNumber = 15
    }
}

// Praktyczne użycie - INotifyPropertyChanged
public class Person : INotifyPropertyChanged
{
    private string _name;
    
    public string Name
    {
        get => _name;
        set
        {
            if (_name != value)
            {
                _name = value;
                OnPropertyChanged();  // Bez parametru!
            }
        }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        // propertyName będzie "Name" automatycznie! ✨
    }
}

Inne przydatne built-in attributes

// [Serializable] - klasa może być serializowana
[Serializable]
public class Person
{
    public string Name { get; set; }
    
    [NonSerialized]  // To pole NIE będzie serializowane
    private int _tempData;
}

// [Required] - walidacja (ASP.NET)
public class UserDto
{
    [Required(ErrorMessage = "Name jest wymagane")]
    public string Name { get; set; }
    
    [Range(18, 100, ErrorMessage = "Age musi być między 18 a 100")]
    public int Age { get; set; }
    
    [EmailAddress(ErrorMessage = "Nieprawidłowy email")]
    public string Email { get; set; }
}

// [JsonPropertyName] - custom JSON property name
public class Product
{
    [JsonPropertyName("product_id")]
    public int Id { get; set; }
    
    [JsonPropertyName("product_name")]
    public string Name { get; set; }
}
// JSON: { "product_id": 1, "product_name": "Laptop" }

// [DebuggerDisplay] - custom display w debuggerze
[DebuggerDisplay("Person: {Name}, Age: {Age}")]
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
// W debuggerze Visual Studio pokaże: "Person: Jan, Age: 30"

// [Flags] - enum jako bit flags
[Flags]
public enum FileAccess
{
    Read = 1,
    Write = 2,
    Execute = 4
}

FileAccess access = FileAccess.Read | FileAccess.Write;  // 3
Console.WriteLine(access);  // "Read, Write" (bez [Flags] byłoby "3")
Custom Attributes - tworzenie własnych

Definiowanie custom attribute

// Custom attribute - dziedziczy po System.Attribute
// Konwencja: nazwa kończy się na "Attribute"

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorAttribute : Attribute
{
    public string Name { get; }
    public string Date { get; set; }  // Optional property
    
    public AuthorAttribute(string name)
    {
        Name = name;
    }
}

// Użycie
[Author("Jan Kowalski", Date = "2026-01-15")]
public class Calculator
{
    [Author("Anna Nowak")]
    public int Add(int a, int b)
    {
        return a + b;
    }
}

// Możesz pominąć "Attribute" suffix
[Author("Jan")]  // Równoważne: [AuthorAttribute("Jan")]
public class Example { }

AttributeUsage - gdzie można użyć attribute

// AttributeUsage określa gdzie attribute może być użyty
[AttributeUsage(
    AttributeTargets.Class | AttributeTargets.Struct,  // Tylko na class i struct
    AllowMultiple = false,  // Tylko jeden na element
    Inherited = true)]      // Dziedziczy na derived classes
public class DocumentationAttribute : Attribute
{
    public string Description { get; }
    
    public DocumentationAttribute(string description)
    {
        Description = description;
    }
}

// AttributeTargets options:
// - Assembly, Module
// - Class, Struct, Enum, Interface, Delegate
// - Constructor, Method, Property, Field, Event, Parameter, ReturnValue
// - GenericParameter
// - All (wszystkie)

// AllowMultiple = true - możesz użyć wiele razy
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestCaseAttribute : Attribute
{
    public object[] Arguments { get; }
    
    public TestCaseAttribute(params object[] args)
    {
        Arguments = args;
    }
}

// Wiele tego samego attribute
[TestCase(1, 2, 3)]
[TestCase(5, 10, 15)]
[TestCase(-1, -2, -3)]
public void TestSum(int a, int b, int expected)
{
    Assert.Equal(expected, a + b);
}

Praktyczne przykłady - custom attributes

// Przykład 1: Validation attribute
[AttributeUsage(AttributeTargets.Property)]
public class MinLengthAttribute : Attribute
{
    public int Length { get; }
    public string ErrorMessage { get; set; }
    
    public MinLengthAttribute(int length)
    {
        Length = length;
    }
    
    public bool IsValid(string value)
    {
        return value?.Length >= Length;
    }
}

public class User
{
    [MinLength(3, ErrorMessage = "Name musi mieć min. 3 znaki")]
    public string Name { get; set; }
    
    [MinLength(8, ErrorMessage = "Password musi mieć min. 8 znaków")]
    public string Password { get; set; }
}

// Przykład 2: Dependency injection attribute
[AttributeUsage(AttributeTargets.Property)]
public class InjectAttribute : Attribute
{
    public string ServiceName { get; set; }
}

public class OrderController
{
    [Inject]
    public IOrderService OrderService { get; set; }
    
    [Inject(ServiceName = "CachedRepository")]
    public IRepository Repository { get; set; }
}

// Przykład 3: API routing attribute
[AttributeUsage(AttributeTargets.Method)]
public class RouteAttribute : Attribute
{
    public string Path { get; }
    public string Method { get; set; } = "GET";
    
    public RouteAttribute(string path)
    {
        Path = path;
    }
}

public class UsersController
{
    [Route("/api/users")]
    public List<User> GetUsers() { /* ... */ }
    
    [Route("/api/users/{id}", Method = "POST")]
    public User CreateUser(int id) { /* ... */ }
}

// Przykład 4: Caching attribute
[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : Attribute
{
    public int DurationSeconds { get; set; } = 60;
    public string Key { get; set; }
}

public class DataService
{
    [Cache(DurationSeconds = 300, Key = "all-users")]
    public List<User> GetAllUsers()
    {
        return _repository.GetUsers();
    }
}
Reflection - runtime introspection

Podstawy Reflection - Type i TypeInfo

// Reflection - badanie typów w runtime
using System.Reflection;

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    
    public void SayHello()
    {
        Console.WriteLine($"Hello, I'm {Name}");
    }
}

// Uzyskanie Type
Type personType = typeof(Person);  // Compile-time
Person person = new Person();
Type personType2 = person.GetType();  // Runtime

// Type info
Console.WriteLine(personType.Name);         // "Person"
Console.WriteLine(personType.FullName);     // "MyNamespace.Person"
Console.WriteLine(personType.IsClass);      // true
Console.WriteLine(personType.IsValueType);  // false

Reflection - properties i methods

// Czytanie properties
Type personType = typeof(Person);

// GetProperties - wszystkie public properties
PropertyInfo[] properties = personType.GetProperties();

foreach (var prop in properties)
{
    Console.WriteLine($"Property: {prop.Name}, Type: {prop.PropertyType}");
}
// Output:
// Property: Name, Type: System.String
// Property: Age, Type: System.Int32

// GetMethods - wszystkie public methods
MethodInfo[] methods = personType.GetMethods();

foreach (var method in methods)
{
    Console.WriteLine($"Method: {method.Name}");
}
// Output:
// Method: SayHello
// Method: get_Name  (property getter)
// Method: set_Name  (property setter)
// Method: get_Age
// Method: set_Age
// Method: GetType   (z Object)
// Method: ToString  (z Object)
// ... etc.

// Specific method
MethodInfo sayHello = personType.GetMethod("SayHello");
Console.WriteLine(sayHello.ReturnType);  // System.Void

Reflection - tworzenie instancji i wywoływanie metod

// Tworzenie instancji przez Reflection
Type personType = typeof(Person);

// Activator.CreateInstance
object personObj = Activator.CreateInstance(personType);

// Cast do Person
Person person = (Person)personObj;

// Ustawianie property values przez Reflection
PropertyInfo nameProp = personType.GetProperty("Name");
nameProp.SetValue(person, "Jan");

PropertyInfo ageProp = personType.GetProperty("Age");
ageProp.SetValue(person, 30);

Console.WriteLine($"{person.Name}, {person.Age}");  // "Jan, 30"

// Wywoływanie metod przez Reflection
MethodInfo sayHelloMethod = personType.GetMethod("SayHello");
sayHelloMethod.Invoke(person, null);  // "Hello, I'm Jan"

// Method z parametrami
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

Type calcType = typeof(Calculator);
object calc = Activator.CreateInstance(calcType);

MethodInfo addMethod = calcType.GetMethod("Add");
object result = addMethod.Invoke(calc, new object[] { 5, 3 });
Console.WriteLine(result);  // 8

Reflection - czytanie attributes

// Czytanie attributes przez Reflection
[Author("Jan Kowalski", Date = "2026-01-15")]
public class Calculator
{
    [Obsolete("Użyj AddNumbers()")]
    public int Add(int a, int b)
    {
        return a + b;
    }
    
    public int AddNumbers(int a, int b)
    {
        return a + b;
    }
}

Type calcType = typeof(Calculator);

// Sprawdź czy typ ma attribute
bool hasAuthor = calcType.IsDefined(typeof(AuthorAttribute), false);
Console.WriteLine(hasAuthor);  // true

// Pobierz attribute
AuthorAttribute author = calcType.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine($"Author: {author.Name}, Date: {author.Date}");
// Output: "Author: Jan Kowalski, Date: 2026-01-15"

// Attributes z metod
MethodInfo addMethod = calcType.GetMethod("Add");
ObsoleteAttribute obsolete = addMethod.GetCustomAttribute<ObsoleteAttribute>();

if (obsolete != null)
{
    Console.WriteLine($"Method is obsolete: {obsolete.Message}");
    // Output: "Method is obsolete: Użyj AddNumbers()"
}

// Wszystkie attributes
Attribute[] allAttributes = Attribute.GetCustomAttributes(calcType);
foreach (var attr in allAttributes)
{
    Console.WriteLine(attr.GetType().Name);
}

Praktyczne przykłady - Reflection

// Przykład 1: Simple dependency injection container
public class Container
{
    private Dictionary<Type, Type> _registrations = new();
    
    public void Register<TInterface, TImplementation>()
    {
        _registrations[typeof(TInterface)] = typeof(TImplementation);
    }
    
    public T Resolve<T>()
    {
        Type implementationType = _registrations[typeof(T)];
        return (T)Activator.CreateInstance(implementationType);
    }
}

// Użycie
var container = new Container();
container.Register<ILogger, ConsoleLogger>();

ILogger logger = container.Resolve<ILogger>();
logger.Log("Message");

// Przykład 2: Property mapper
public static void MapProperties<TSource, TDest>(TSource source, TDest dest)
{
    Type sourceType = typeof(TSource);
    Type destType = typeof(TDest);
    
    foreach (var sourceProp in sourceType.GetProperties())
    {
        var destProp = destType.GetProperty(sourceProp.Name);
        
        if (destProp != null && destProp.CanWrite)
        {
            object value = sourceProp.GetValue(source);
            destProp.SetValue(dest, value);
        }
    }
}

// Użycie
var person = new Person { Name = "Jan", Age = 30 };
var dto = new PersonDto();

MapProperties(person, dto);
Console.WriteLine($"{dto.Name}, {dto.Age}");  // "Jan, 30"

// Przykład 3: Validation framework
public static bool ValidateObject(object obj)
{
    Type type = obj.GetType();
    
    foreach (var prop in type.GetProperties())
    {
        var requiredAttr = prop.GetCustomAttribute<RequiredAttribute>();
        
        if (requiredAttr != null)
        {
            object value = prop.GetValue(obj);
            
            if (value == null || (value is string str && string.IsNullOrEmpty(str)))
            {
                Console.WriteLine($"{prop.Name} is required!");
                return false;
            }
        }
        
        var minLengthAttr = prop.GetCustomAttribute<MinLengthAttribute>();
        
        if (minLengthAttr != null && prop.GetValue(obj) is string strValue)
        {
            if (!minLengthAttr.IsValid(strValue))
            {
                Console.WriteLine(minLengthAttr.ErrorMessage);
                return false;
            }
        }
    }
    
    return true;
}

// Użycie
var user = new User { Name = "Jan" };
bool isValid = ValidateObject(user);
⚠️ Reflection - performance warning!
  • ❌ Reflection jest WOLNE - 10-100x wolniejsze niż direct code
  • ❌ Brak compile-time safety - błędy dopiero w runtime
  • ❌ Trudniejszy debugging i maintenance
  • ✅ Używaj Reflection tylko gdy MUSISZ (frameworks, tools, dynamic scenarios)
  • ✅ Cachuj Type/MethodInfo objects - nie wywołuj GetType() w pętli
  • ✅ W 2026 rozważ Source Generators zamiast Reflection!
🔥 Source Generators - nowoczesna alternatywa (C# 9+)

Problem z Reflection - runtime overhead

// Problem: Reflection w runtime
public class JsonSerializer
{
    public string Serialize(object obj)
    {
        Type type = obj.GetType();  // Reflection
        var properties = type.GetProperties();  // Reflection
        
        var json = "{";
        foreach (var prop in properties)
        {
            object value = prop.GetValue(obj);  // Reflection - WOLNE!
            json += $"\"{prop.Name}\": \"{value}\",";
        }
        json += "}";
        
        return json;
    }
}

// Każde serialize() wywołuje Reflection - wolne! 😱

Source Generators - compile-time code generation!

🎉 C# 9+ - Source Generators

Source Generators = generują kod w COMPILE-TIME, nie runtime!
Zero reflection overhead, pełna compile-time safety! ✨

// Source Generator generuje kod w compile-time
// Zamiast Reflection w runtime, masz wygenerowany kod!

// Twój kod:
[AutoSerialize]
public partial class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Source Generator generuje (w compile-time):
public partial class Person
{
    public string ToJson()
    {
        return $"{{\"Name\":\"{Name}\",\"Age\":{Age}}}";
    }
}

// Użycie - zero Reflection!
var person = new Person { Name = "Jan", Age = 30 };
string json = person.ToJson();  // Wygenerowana metoda - szybka! ✨

// Nie ma Reflection overhead!

Source Generators - przykłady z .NET

// Przykład 1: System.Text.Json Source Generator
// Zamiast Reflection - generuje serialization code!

[JsonSerializable(typeof(Person))]
public partial class PersonJsonContext : JsonSerializerContext { }

// .NET generuje kod serialization w compile-time
var person = new Person { Name = "Jan", Age = 30 };
string json = JsonSerializer.Serialize(person, PersonJsonContext.Default.Person);
// Zero Reflection - wszystko wygenerowane! ✨

// Przykład 2: Regex Source Generator
// Zamiast Reflection - generuje regex code!

public partial class RegexHelpers
{
    [GeneratedRegex(@"\d{3}-\d{3}-\d{4}")]
    public static partial Regex PhoneNumber();
}

// .NET generuje zoptymalizowany regex code w compile-time
bool isValid = RegexHelpers.PhoneNumber().IsMatch("123-456-7890");
// Zero overhead! ✨

// Przykład 3: Logging Source Generator
public partial class Logger
{
    [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "User {name} logged in")]
    public static partial void LogUserLogin(ILogger logger, string name);
}

// .NET generuje logging code - bez boxing, bez allocations!
Logger.LogUserLogin(_logger, "Jan");  // Super fast! ✨

Reflection vs Source Generators - porównanie

Feature Reflection (Runtime) Source Generators (Compile-time)
Kiedy działa Runtime Compile-time
Performance Wolne (10-100x) Szybkie (jak ręczny kod)
Type safety Runtime errors Compile-time errors
Debugging Trudne Możesz debugować wygenerowany kod
AOT compatible ❌ Problemy z trimming ✅ Pełne wsparcie
Use case Dynamic scenarios, tools Known types w compile-time
💡 Kiedy używać czego?
  • Source Generators (preferowane w 2026!)
    • ✅ Serialization (JSON, XML, etc.)
    • ✅ Mapping (AutoMapper-like)
    • ✅ Validation
    • ✅ Dependency injection registration
    • ✅ ORM queries (EF-like)
  • Reflection (tylko gdy musisz)
    • ✅ Plugin systems
    • ✅ Development tools
    • ✅ Truly dynamic scenarios
    • ✅ Working z unknown types
Podsumowanie

Attributes i Reflection - metaprogramowanie w C#!

  • Attributes - metadane o kodzie, deklaratywne programming
  • [Obsolete] - deprecation warnings i errors
  • [Conditional] - conditional compilation, zero overhead
  • Caller info attributes - CallerMemberName, CallerFilePath, CallerLineNumber
  • Custom attributes - własne attributes, AttributeUsage, AllowMultiple
  • Reflection basics - Type, PropertyInfo, MethodInfo
  • Reflection operations - tworzenie instancji, wywoływanie metod, czytanie attributes
  • ⚠️ Performance - Reflection jest wolne, cachuj objects
  • 🔥 Source Generators - compile-time alternative, zero overhead!
  • Reflection vs Generators - kiedy czego używać

W kolejnym wpisie poznasz tzw. partial types - klasy, metody i właściwości!

Zadanie dla Ciebie 🎯

Stwórz prosty validation framework używając attributes i reflection:

// Custom validation attributes
[AttributeUsage(AttributeTargets.Property)]
public class RequiredAttribute : ValidationAttribute { }

[AttributeUsage(AttributeTargets.Property)]
public class MinLengthAttribute : ValidationAttribute
{
    public int Length { get; }
    public MinLengthAttribute(int length) { Length = length; }
}

[AttributeUsage(AttributeTargets.Property)]
public class RangeAttribute : ValidationAttribute
{
    public int Min { get; }
    public int Max { get; }
    public RangeAttribute(int min, int max) { Min = min; Max = max; }
}

// Validator class
public static class Validator
{
    public static ValidationResult Validate(object obj)
    {
        // Use Reflection to:
        // 1. Get all properties
        // 2. Check for validation attributes
        // 3. Validate values
        // 4. Return ValidationResult with errors
    }
}

// Model
public class User
{
    [Required(ErrorMessage = "Name is required")]
    [MinLength(3, ErrorMessage = "Name must be at least 3 characters")]
    public string Name { get; set; }
    
    [Range(18, 100, ErrorMessage = "Age must be between 18 and 100")]
    public int Age { get; set; }
}

// Użycie
var user = new User { Name = "Jo", Age = 17 };
var result = Validator.Validate(user);

if (!result.IsValid)
{
    foreach (var error in result.Errors)
    {
        Console.WriteLine(error);
    }
}
🎯 BONUS: Dependency Injection Container z Reflection

Stwórz prosty DI container używając Reflection!

Do zaimplementowania:

  1. Container class:
    • Register<TInterface, TImplementation>() - rejestracja
    • RegisterSingleton<TInterface, TImplementation>()
    • Resolve<T>() - tworzenie instancji
  2. Constructor injection:
    • Automatyczna analiza constructor parameters
    • Recursive resolution dependencies
    • Circular dependency detection
  3. Property injection (opcjonalne):
    • [Inject] attribute na properties
    • Automatyczne wypełnianie properties
  4. Lifetime management:
    • Transient - nowa instancja za każdym razem
    • Singleton - jedna instancja
    • Scoped - jedna instancja per scope

Przykład użycia:

// Services
public interface ILogger { void Log(string msg); }
public class ConsoleLogger : ILogger 
{ 
    public void Log(string msg) => Console.WriteLine(msg); 
}

public interface IRepository { List<User> GetUsers(); }
public class UserRepository : IRepository 
{
    private readonly ILogger _logger;
    
    public UserRepository(ILogger logger)  // Constructor injection
    {
        _logger = logger;
    }
    
    public List<User> GetUsers() 
    { 
        _logger.Log("Getting users");
        return new List<User>(); 
    }
}

public class UserService
{
    private readonly IRepository _repository;
    
    [Inject]  // Property injection
    public ILogger Logger { get; set; }
    
    public UserService(IRepository repository)
    {
        _repository = repository;
    }
    
    public void ProcessUsers()
    {
        Logger.Log("Processing users");
        var users = _repository.GetUsers();
    }
}

// Setup container
var container = new Container();
container.RegisterSingleton<ILogger, ConsoleLogger>();
container.Register<IRepository, UserRepository>();
container.Register<UserService, UserService>();

// Resolve
var service = container.Resolve<UserService>();
service.ProcessUsers();

// Container automatycznie:
// 1. Tworzy ConsoleLogger (singleton)
// 2. Tworzy UserRepository z ILogger dependency
// 3. Tworzy UserService z IRepository dependency
// 4. Wypełnia Logger property w UserService

Ten projekt demonstruje zaawansowane Reflection - constructor analysis, recursive resolution, lifetime management! 🚀💉