Back to: C# New Features Tutorials
Interceptors in C# with Examples
In this article, I will discuss Interceptors in C# with Examples. Please read our previous article discussing Experimental Attribute in C# with Examples. Interceptors in C# 12 provide a way to inject behavior into methods dynamically, without modifying the method’s original code. This powerful feature helps in logging, validation, performance tracking, or any cross-cutting concerns. In this article, we’ll explore how to use interceptors in C# 12 using Reflection, Castle DynamicProxy, and PostSharp to intercept method calls and add extra behavior like logging.
What Are Interceptors?
Interceptors in C# allow you to “intercept” method calls to add custom behavior such as logging, caching, error handling, and validation. This is particularly useful in scenarios where you want to apply the same behavior across multiple methods or classes without modifying the original code.
- Method-Level Interception: You can intercept individual method calls and add logic before or after the method executes.
- Cross-Cutting Concerns: Interceptors are commonly used for logging, caching, validation, and performance monitoring.
- Flexibility: Interceptors can be used dynamically with libraries like Castle DynamicProxy or PostSharp for more complex scenarios.
Example: Using Reflection to Implement Method Interception
Reflection is one way to intercept methods and inject additional behavior. In this example, we will define a custom interceptor using an attribute and use Reflection to invoke the method while applying the interceptor logic.
using System.Reflection; namespace CSharp12NewFeatures { // Define a custom interceptor attribute [AttributeUsage(AttributeTargets.Method)] public class LogInterceptorAttribute : Attribute { // Define the action to be executed before the method call public void BeforeInvocation(string methodName) { Console.WriteLine($"Entering method: {methodName}"); } // Define the action to be executed after the method call public void AfterInvocation(string methodName) { Console.WriteLine($"Exiting method: {methodName}"); } } // Example class to demonstrate method interception public class OrderService { [LogInterceptor] public void CreateOrder(int orderId) { // Simulate some business logic Console.WriteLine($"Creating order with ID: {orderId}"); } } // A method to dynamically call a method and apply interception public class MethodInterceptor { public static void InvokeWithInterceptor(object target, string methodName, params object[] parameters) { // Get the method info for the target object MethodInfo method = target.GetType().GetMethod(methodName); // Check if the method exists and has the LogInterceptor attribute if (method != null) { // Retrieve the custom attribute safely var attribute = (LogInterceptorAttribute)method.GetCustomAttribute(typeof(LogInterceptorAttribute)); if (attribute != null) { // Invoke the 'BeforeInvocation' logic before calling the method attribute.BeforeInvocation(methodName); } // Invoke the actual method method.Invoke(target, parameters); if (attribute != null) { // Invoke the 'AfterInvocation' logic after calling the method attribute.AfterInvocation(methodName); } } else { Console.WriteLine($"Method '{methodName}' not found on target object."); } } } public class Program { static void Main(string[] args) { var orderService = new OrderService(); // Using MethodInterceptor to invoke the method with the interceptor applied MethodInterceptor.InvokeWithInterceptor(orderService, "CreateOrder", 101); } } }
Code Explanation:
- LogInterceptorAttribute: The LogInterceptor attribute defines the BeforeInvocation and AfterInvocation methods that will log messages before and after the method call.
- MethodInterceptor: This class uses Reflection to find the method with the LogInterceptor attribute and applies the interceptor logic around the method call.
- OrderService: The CreateOrder method in this class is intercepted using the LogInterceptor attribute, which logs messages before and after the method is executed.
Output:
Example: Using Castle DynamicProxy for Method Interception
First, install the Castle.Core package: Install-Package Castle.Core
Castle DynamicProxy is a popular library that allows us to create proxy objects and intercept method calls. This is particularly useful when you need to apply interceptors to existing classes dynamically without modifying them.
using Castle.DynamicProxy; namespace CSharp12NewFeatures { // Logging and Caching Interceptor public class LoggingAndCachingInterceptor : IInterceptor { private static readonly Dictionary<int, string> _cache = new(); public void Intercept(IInvocation invocation) { string methodName = invocation.Method.Name; // Logging: Log before invocation Console.WriteLine($"[LOG] Entering method: {methodName}"); // Execute the actual method invocation.Proceed(); // Caching: Apply caching logic after method invocation if (invocation.Method.Name == "GetOrderDetails") { int orderId = (int)invocation.Arguments[0]; if (_cache.ContainsKey(orderId)) { Console.WriteLine($"[CACHE] Returning cached result for OrderID: {orderId}"); } else { _cache[orderId] = $"Order {orderId} details fetched at {DateTime.Now}"; Console.WriteLine($"[CACHE] Caching result for OrderID: {orderId}"); } } } } // OrderService class with caching and logging public class OrderService { public virtual string GetOrderDetails(int orderId) { return $"Fetching order {orderId} details from database."; } } public class Program { static void Main(string[] args) { // Create a ProxyGenerator to generate a proxy for the OrderService class var proxyGenerator = new ProxyGenerator(); // Create an interceptor instance that handles logging and caching var interceptor = new LoggingAndCachingInterceptor(); // Generate a proxy for OrderService with the interceptor var orderServiceProxy = proxyGenerator.CreateClassProxy<OrderService>(interceptor); // Simulating first API call Console.WriteLine(orderServiceProxy.GetOrderDetails(101)); // Simulating second API call with caching Console.WriteLine(orderServiceProxy.GetOrderDetails(101)); } } }
Code Explanations:
- Create an Interceptor: LoggingAndCachingInterceptor implements IInterceptor from Castle.DynamicProxy. The Intercept method is called whenever a method is invoked on the proxied object.
- Inside the Intercept method: We log the method call. After invoking the method (using invocation.Proceed()), we check the cache for the result and apply caching.
- Generate the Proxy: We use ProxyGenerator to create a proxy for the OrderService class. The proxy intercepts method calls and applies the LoggingAndCachingInterceptor logic. The CreateClassProxy method of ProxyGenerator creates a proxy of the OrderService class with the interceptor applied.
- Simulating Method Calls: The proxy instance of OrderService behaves like the original OrderService, but now each method call is intercepted by the LoggingAndCachingInterceptor.
Output:
Explanation of How It Works:
Castle DynamicProxy allows you to create proxies for classes at runtime. The proxy intercepts method calls and applies the logic from the LoggingAndCachingInterceptor.
- Method Interception: When GetOrderDetails(101) is called, the interceptor logs the call and checks if the result is already cached. If it’s cached, it returns the cached result; otherwise, it proceeds to execute the method and caches the result.
- Interceptor Behavior: The IInterceptor interface defines the Intercept method. In this method, we first log the method invocation, proceed to invoke the original method, and finally apply caching logic based on the method’s result.
The interceptor feature in C# 12 introduces a clean and elegant way to modify the behavior of methods. By applying interceptors via attributes, developers can easily add pre-processing, post-processing, and caching behavior to methods without modifying the method’s code directly. This feature is ideal for implementing cross-cutting concerns like logging, validation, and caching.
In this article, I explain Interceptors in C# with Examples. I want your feedback. Please post your feedback, questions, or comments about this article.