Версия 1.1 от Alexandr Fokin на 2026/03/18 15:24

Скрыть последних авторов
Alexandr Fokin 1.1 1 | |Пример создание тестового декоратора, в котором в одном методе задается кастомное поведение, а другие методы пробрасывают поведение исходной реализации.
2 | |[[Reflection ~~~| Рефлексия>>doc:Разработка.NET.C#.Рантайм и типы.Reflection | Рефлексия .WebHome]], [[DI IOC AOP>>doc:Разработка.NET.Библиотеки.DI IOC AOP.WebHome]]
3 | |{{code language="c#"}}public static void RegistryDecorator(IServiceCollection services)
4 {
5     var sourceRegistration = services.First(
6         e => e.ServiceType.FullName == "IService");
7
8     services.Remove(sourceRegistration);
9     services.Add(new ServiceDescriptor(
10         sourceRegistration.ImplementationType,
11         sourceRegistration.ImplementationType,
12         ServiceLifetime.Singleton));
13     services.Add(new ServiceDescriptor(
14         sourceRegistration.ServiceType,
15         (s) => BuildDecorator(
16             sourceRegistration,
17             s.GetRequiredService(sourceRegistration.ImplementationType)),
18         ServiceLifetime.Singleton));
19 }
20
21 private static object BuildDecorator(
22     ServiceDescriptor descriptor,
23     object source)
24 {
25     static IEnumerable<MethodInfo> GetInterfaceMethodsRecursive(Type type)
26     {
27         foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public))
28         {
29             yield return method;
30         }
31
32         if (type.IsInterface)
33         {
34             foreach (var iface in type.GetInterfaces())
35             {
36                 foreach (var method in GetInterfaceMethodsRecursive(iface))
37                 {
38                     yield return method;
39                 }
40             }
41         }
42     }
43
44     static object[] CreateFakeParameters(MethodInfo method)
45     {
46         var parameters = method.GetParameters();
47         var result = new List<object>(parameters.Length);
48
49         foreach (var elem in parameters)
50         {
51             var type = !elem.ParameterType.IsByRef
52                 ? elem.ParameterType
53                 : elem.ParameterType.GetElementType();
54             var value = !type.IsValueType
55                 ? null
56                 : Activator.CreateInstance(type);
57             result.Add(value);
58         }
59
60         return result.ToArray();
61     }
62
63     var decorator = Substitute.For(
64         new[] { descriptor.ServiceType },
65         Array.Empty<object>());
66
67     var methodsForDecoration = GetInterfaceMethodsRecursive(descriptor.ServiceType)
68         .ToHashSet();
69
70     // Подменяем поведение нужного метода.
71     // string Action1(string text)
72     var customMethod = methodsForDecoration.Single(e => e.Name == "Action1");
73     customMethod
74         .Invoke(decorator, CreateFakeParameters(customMethod))
75         .ReturnsForAnyArgs(
76             e =>
77             {
78                 return $"Test {e[0]}";
79             });
80     methodsForDecoration.Remove(customMethod);
81
82     // Пробрасываем остальные методы интерфейса без изменения.
83     foreach (var elem in methodsForDecoration)
84     {
85         elem
86             .Invoke(decorator, CreateFakeParameters(elem))
87             .ReturnsForAnyArgs(
88                 e =>
89                 {
90                     var parameters = e.Args();
91                     var result = elem.Invoke(source, parameters);
92
93                     // для передачи out параметров.
94                     for (var i = 0; i < parameters.Length; i++)
95                     {
96                         e[i] = parameters[i];
97                     }
98
99                     return result;
100                 });
101     }
102
103     // invoke test
104     // var p = CreateFakeParameters(customMethod);
105     // p[0] = "name";
106     // var r = customMethod.Invoke(decorator, p);
107
108     return decorator;
109 }
110 {{/code}}
111 | |
112
113