今天我们来介绍一下C#中的动态代理(Dynamic Proxy)技术。动态代理是一种在运行时创建代理类的技术,它允许我们在不修改原始类代码的情况下,拦截和增强方法调用。这在AOP(面向切面编程)中非常有用,可以用于日志记录、事务管理、权限验证等场景。 动态代理的实现通常依赖于反射和IL生成技术。C#中有几个流行的库可以帮助我们实现动态代理,最常用的包括Castle DynamicProxy和DispatchProxy。
Castle DynamicProxy Castle DynamicProxy 是一个功能强大的动态代理库。它允许我们创建接口代理和类代理。下面是一个使用Castle DynamicProxy创建动态代理的简单示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 using System;using Castle.DynamicProxy;public class LoggingInterceptor : IInterceptor { public void Intercept (IInvocation invocation ) { Console.WriteLine($"Calling method {invocation.Method.Name} with arguments {string .Join(", " , invocation.Arguments)} " ); invocation.Proceed(); Console.WriteLine($"Method {invocation.Method.Name} returned {invocation.ReturnValue} " ); } } public interface ICalculator { int Add (int a, int b ) ; } public class Calculator : ICalculator { public int Add (int a, int b ) => a + b; } class Program { static void Main () { ProxyGenerator generator = new ProxyGenerator(); ICalculator calculatorProxy = generator.CreateInterfaceProxyWithTarget<ICalculator>( new Calculator(), new LoggingInterceptor()); int result = calculatorProxy.Add(2 , 3 ); Console.WriteLine($"Result: {result} " ); } }
输出为: Calling method Add with arguments 2, 3 Method Add returned 5 Result: 5
DispatchProxy DispatchProxy 是.NET Framework 4.6及更高版本中引入的一个内置动态代理机制。它允许我们创建接口的代理类,并通过重写Invoke方法来拦截方法调用。下面是一个使用DispatchProxy创建动态代理的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 using System;using System.Reflection;public class LoggingProxy <T > : DispatchProxy { private T _decorated; protected override object Invoke (MethodInfo targetMethod, object [] args ) { Console.WriteLine($"Calling method {targetMethod.Name} with arguments {string .Join(", " , args )} " ); var result = targetMethod.Invoke(_decorated, args ); Console.WriteLine($"Method {targetMethod.Name} returned {result} " ); return result; } public static T Create (T decorated ) { object proxy = Create<T, LoggingProxy<T>>(); ((LoggingProxy<T>)proxy)._decorated = decorated; return (T)proxy; } } public interface ICalculator { int Add (int a, int b ) ; } public class Calculator : ICalculator { public int Add (int a, int b ) => a + b; } class Program { static void Main () { ICalculator calculator = new Calculator(); ICalculator proxy = LoggingProxy<ICalculator>.Create(calculator); int result = proxy.Add(2 , 3 ); Console.WriteLine($"Result: {result} " ); } }
输出为: Calling method Add with arguments 2, 3 Method Add returned 5 Result: 5
通过以上两个示例,我们可以看到动态代理在C#中的强大功能。无论是使用Castle DynamicProxy还是DispatchProxy,我们都可以轻松地拦截方法调用并添加自定义逻辑,从而实现AOP的各种需求。在实际应用中,选择哪种动态代理技术取决于具体的需求和项目环境。
只有被标记为virtual的方法才能被动态代理拦截。接口的方法默认就是virtual的。具体实现类中的virtual方法也可以被拦截。
动态代理的应用场景 动态代理在许多场景中都非常有用,以下是一些常见框架及其对应的动态代理应用场景:
Moq Moq是一个流行的.NET模拟库,广泛用于单元测试。它利用动态代理技术创建接口和类的模拟对象,使得测试代码更加简洁和易于维护。通过Moq,我们可以轻松地设置期望值、验证方法调用以及模拟复杂的行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using Moq;using System.Diagnostics;public class Customer { public int Id { get ; set ; } } public interface IDataService { Customer GetCustomer (int id ) ; } var mock = new Mock<IDataService>();mock.Setup(x => x.GetCustomer(It.IsAny<int >())) .Returns((int id) => new Customer { Id = id }) .Callback((int id) => Console.WriteLine($"Called with id: {id} " )); var result = mock.Object.GetCustomer(1 );mock.Verify(s => s.GetCustomer(1 ), Times.Once); Debug.Assert(result.Id == 1 );
Moq的核心是表达式树解析 ,将 x => x.Method(It.IsAny<int>()) 转换为匹配规则动态代理处理所有方法调用 ,不需要手动实现每个接口方法调用链是惰性计算的 ,只有在实际调用时才计算结果默认值提供机制 让 Mock.Of() 等特性成为可能 这就是为什么 Moq 能”自动”实现所有接口方法 - 它实际上没有实现任何方法,而是用一个动态代理拦截所有调用,然后根据 Setup 规则决定如何响应。
Entity Framework Core Entity Framework Core使用动态代理来实现延迟加载(Lazy Loading)功能。当我们访问导航属性时,EF Core会动态创建一个代理类来拦截对该属性的访问,并在需要时从数据库加载相关数据。这种方式提高了性能,避免了不必要的数据加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using Microsoft.EntityFrameworkCore;public class Blog { public int BlogId { get ; set ; } public string Url { get ; set ; } public virtual ICollection<Post> Posts { get ; set ; } } public class Post { public int PostId { get ; set ; } public string Title { get ; set ; } public int BlogId { get ; set ; } public virtual Blog Blog { get ; set ; } }
AOP框架 许多AOP框架(如PostSharp、AspectCore等)都使用动态代理来实现方法拦截和增强功能。通过动态代理,这些框架可以在不修改原始代码的情况下,添加日志记录、事务管理、缓存等功能,从而提高代码的可维护性和复用性。
1 2 3 4 5 [Log ] public void ProcessData (){ }
远程调用 动态代理也常用于远程调用场景,如WCF和gRPC等框架。通过动态代理,客户端可以透明地调用远程服务,就像调用本地方法一样。动态代理负责处理网络通信、序列化和反序列化等复杂操作,使得开发者可以专注于业务逻辑的实现。
1 2 3 4 5 var channel = GrpcChannel.ForAddress("https://localhost:5001" );var client = new Greeter.GreeterClient(channel);var reply = await client.SayHelloAsync(new HelloRequest { Name = "World" });Console.WriteLine("Greeting: " + reply.Message);
装饰器模式 动态代理也可以用于实现装饰器模式,通过动态创建代理类来增强原始对象的功能。例如,我们可以创建一个缓存代理,当调用某个方法时,先检查缓存中是否有结果,如果有则直接返回,否则调用原始方法并将结果存入缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class CachingProxy <T > : DispatchProxy { private T _decorated; private Dictionary<string , object > _cache = new Dictionary<string , object >(); protected override object Invoke (MethodInfo targetMethod, object [] args ) { string cacheKey = $"{targetMethod.Name} :{string .Join("," , args )} " ; if (_cache.TryGetValue(cacheKey, out var cachedResult)) { return cachedResult; } var result = targetMethod.Invoke(_decorated, args ); _cache[cacheKey] = result; return result; } public static T Create (T decorated ) { object proxy = Create<T, CachingProxy<T>>(); ((CachingProxy<T>)proxy)._decorated = decorated; return (T)proxy; } } ICalculator calculator = new Calculator(); ICalculator proxy = CachingProxy<ICalculator>.Create(calculator); int result1 = proxy.Add(2 , 3 );
总结 动态代理是C#中一种强大的技术,广泛应用于AOP、单元测试和远程调用等场景。通过使用动态代理,我们可以在不修改原始代码的情况下,拦截和增强方法调用,从而提高代码的灵活性和可维护性。无论是使用Castle DynamicProxy还是DispatchProxy,动态代理都为C#开发者提供了强大的工具,帮助他们更好地实现复杂的功能需求。
参考资料