10  Python @decorator vs C# Decorator Pattern

You’re right to notice the similarity! They share the same core concept (wrapping to add behavior), but they work at different levels. Let me break it down:

10.1 The Key Similarity

BOTH wrap something to add behavior without modifying it

Python @decorator              C# Decorator Pattern
─────────────────              ────────────────────
Wraps functions/methods        Wraps objects/classes
At definition time             At runtime
Function-level                 Object-level

10.2 Python @decorator - Function Wrapping

# Python decorator (function-level)
def logging_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"[LOG] Calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"[LOG] {func.__name__} returned {result}")
        return result
    return wrapper

@logging_decorator  # Syntactic sugar for: save_patient = logging_decorator(save_patient)
def save_patient(patient_name):
    print(f"Saving {patient_name} to database")
    return "Success"

# Usage
save_patient("John Doe")

# Output:
# [LOG] Calling save_patient
# Saving John Doe to database
# [LOG] save_patient returned Success

10.3 C# Decorator Pattern - Object Wrapping

// C# Decorator pattern (object-level)
public interface IRepository
{
    string SavePatient(string patientName);
}

public class SqlRepository : IRepository
{
    public string SavePatient(string patientName)
    {
        Console.WriteLine($"Saving {patientName} to database");
        return "Success";
    }
}

public class LoggingRepositoryDecorator : IRepository
{
    private readonly IRepository _inner;
    
    public LoggingRepositoryDecorator(IRepository inner)
    {
        _inner = inner;
    }
    
    public string SavePatient(string patientName)
    {
        Console.WriteLine($"[LOG] Calling SavePatient");
        var result = _inner.SavePatient(patientName);
        Console.WriteLine($"[LOG] SavePatient returned {result}");
        return result;
    }
}

// Usage - manual wrapping at runtime
IRepository repo = new SqlRepository();
repo = new LoggingRepositoryDecorator(repo);
repo.SavePatient("John Doe");

10.4 Side-by-Side Comparison

PYTHON @DECORATOR                 C# DECORATOR PATTERN
═════════════════════════════════════════════════════════════

Definition Time                   Runtime
─────────────────────────────────────────────────────────────
@logging                          IRepository repo = 
@caching                            new SqlRepository();
@retry                            repo = new CachingDecorator(repo);
def save_patient():               repo = new LoggingDecorator(repo);
    ...                           repo = new RetryDecorator(repo);


Function-based                    Object-based
─────────────────────────────────────────────────────────────
Wraps individual functions        Wraps entire objects with
                                  all their methods


Syntax                            Composition
─────────────────────────────────────────────────────────────
Built into language (@)           Manual object composition
Declarative                       Explicit


Scope                             Interface
─────────────────────────────────────────────────────────────
Works on functions/methods        Requires interface/base class
Less formal structure             More formal OOP structure

10.5 Python CAN Do Object-Level Decoration!

Python’s @decorator syntax can actually implement the GoF Decorator pattern:

# Python implementing GoF Decorator pattern (object-level)

from abc import ABC, abstractmethod

# Interface
class IRepository(ABC):
    @abstractmethod
    def save_patient(self, patient_name):
        pass

# Core implementation
class SqlRepository(IRepository):
    def save_patient(self, patient_name):
        print(f"Saving {patient_name} to database")
        return "Success"

# Decorator class (like C#)
class LoggingRepositoryDecorator(IRepository):
    def __init__(self, inner_repository):
        self._inner = inner_repository
    
    def save_patient(self, patient_name):
        print(f"[LOG] Calling save_patient")
        result = self._inner.save_patient(patient_name)
        print(f"[LOG] Returned {result}")
        return result

class CachingRepositoryDecorator(IRepository):
    def __init__(self, inner_repository):
        self._inner = inner_repository
        self._cache = {}
    
    def save_patient(self, patient_name):
        if patient_name in self._cache:
            print(f"[CACHE HIT] {patient_name}")
            return self._cache[patient_name]
        
        result = self._inner.save_patient(patient_name)
        self._cache[patient_name] = result
        return result

# Usage - exactly like C#!
repo = SqlRepository()
repo = CachingRepositoryDecorator(repo)
repo = LoggingRepositoryDecorator(repo)

repo.save_patient("John Doe")

10.6 Visual Comparison

PYTHON FUNCTION DECORATOR
═══════════════════════════════════════════════════

@decorator3       ┌─────────────────────┐
@decorator2       │ decorator3(         │
@decorator1  ───→ │   decorator2(       │ ─→ Wraps at
def func():       │     decorator1(     │    definition time
    ...           │       func          │
                  │     )               │
                  │   )                 │
                  │ )                   │
                  └─────────────────────┘


C# OBJECT DECORATOR
═══════════════════════════════════════════════════

obj = Core()      ┌─────────────────────┐
obj = Dec1(obj)   │ Decorator3(         │
obj = Dec2(obj)   │   Decorator2(       │ ─→ Wraps at
obj = Dec3(obj)   │     Decorator1(     │    runtime
                  │       Core          │
                  │     )               │
                  │   )                 │
                  │ )                   │
                  └─────────────────────┘

10.7 Python’s Function Decorators for Cross-Cutting Concerns

Python’s @decorator is actually MORE convenient for function-level cross-cutting concerns:

# Python makes this super easy!
import time
from functools import wraps

def timing_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.2f}s")
        return result
    return wrapper

def retry_decorator(max_attempts=3):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f"Retry {attempt + 1}/{max_attempts}")
        return wrapper
    return decorator

# Stack them easily!
@timing_decorator
@retry_decorator(max_attempts=3)
def process_dicom_study(study_id):
    print(f"Processing {study_id}")
    return "Done"

# Clean and declarative!

In C#, you’d need aspect-oriented programming libraries or manual decorator classes for this.

10.8 When to Use Each

┌──────────────────────────────────────────────────────────┐
│ USE PYTHON @DECORATOR WHEN:                              │
│ ═══════════════════════════════════════════              │
│                                                          │
│ ✓ Wrapping individual functions/methods                  │
│ ✓ Cross-cutting concerns at function level               │
│ ✓ Simple, declarative syntax preferred                   │
│ ✓ Same decorator applied to many functions               │
│                                                          │
│ Examples:                                                │
│   @login_required                                        │
│   @cache_result                                          │
│   @measure_time                                          │
│   @validate_input                                        │
└──────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────┐
│ USE C# DECORATOR PATTERN WHEN:                           │
│ ═══════════════════════════════════════════              │
│                                                          │
│ ✓ Wrapping entire objects/services                       │
│ ✓ Runtime composition needed                             │
│ ✓ Dependency Injection integration                       │
│ ✓ Complex object behavior modification                   │
│                                                          │
│ Examples:                                                │
│   IRepository → LoggingDecorator → CachingDecorator      │
│   IDicomProcessor → AnonymizingDecorator                 │
│   IEmailService → RetryDecorator                         │
└──────────────────────────────────────────────────────────┘

10.9 Real-World Python + C# Example (Your Context!)

10.9.1 Python (FastAPI for AI model serving)

from functools import wraps
import time

# Function decorators for cross-cutting concerns
def log_inference(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[INFERENCE] Starting {func.__name__}")
        result = func(*args, **kwargs)
        print(f"[INFERENCE] Completed {func.__name__}")
        return result
    return wrapper

def measure_performance(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        duration = time.time() - start
        print(f"[PERF] {func.__name__}: {duration:.3f}s")
        return result
    return wrapper

@log_inference
@measure_performance
def predict_lung_nodule(dicom_image):
    # AI inference logic
    return {"nodule_detected": True, "confidence": 0.95}

# Usage
predict_lung_nodule(image_data)

10.9.2 C# (.NET API for PACS integration)

// Object decorators for service composition
public interface IPacsClient
{
    DicomStudy FetchStudy(string studyId);
}

public class VnaPacsClient : IPacsClient
{
    public DicomStudy FetchStudy(string studyId) { ... }
}

public class LoggingPacsDecorator : IPacsClient
{
    private readonly IPacsClient _inner;
    public LoggingPacsDecorator(IPacsClient inner) => _inner = inner;
    
    public DicomStudy FetchStudy(string studyId)
    {
        Console.WriteLine($"[LOG] Fetching {studyId}");
        var result = _inner.FetchStudy(studyId);
        Console.WriteLine($"[LOG] Fetched {studyId}");
        return result;
    }
}

public class RetryPacsDecorator : IPacsClient
{
    private readonly IPacsClient _inner;
    public RetryPacsDecorator(IPacsClient inner) => _inner = inner;
    
    public DicomStudy FetchStudy(string studyId)
    {
        // Retry logic
        for (int i = 0; i < 3; i++)
        {
            try { return _inner.FetchStudy(studyId); }
            catch { if (i == 2) throw; }
        }
        throw new Exception("Max retries");
    }
}

// Compose at runtime
IPacsClient client = new VnaPacsClient();
client = new RetryPacsDecorator(client);
client = new LoggingPacsDecorator(client);

10.11 Summary

Yes, you’re absolutely right that they’re conceptually similar!

  • Python’s @decorator is syntactic sugar for function wrapping, making it super convenient for function-level cross-cutting concerns
  • C# Decorator Pattern is the formal GoF pattern for object wrapping, requiring more boilerplate but offering more structure for complex object composition

Think of Python’s @decorator as a language feature that makes a specific use case (function decoration) really easy, while C#’s Decorator Pattern is a design pattern that you implement manually for object composition.

You can actually use both in the same project: Python @decorators for your AI inference functions, and C# Decorator Pattern for your PACS/repository services!