Редактировал(а) Alexandr Fokin 2026/03/18 15:24

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