From e6092ed4e580d857bf23c72b58107751df08f6b5 Mon Sep 17 00:00:00 2001 From: Monday Date: Sat, 31 Jan 2026 01:07:21 +0800 Subject: [PATCH 1/2] Fix #297: Remove invalid Tars.Net link from README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1c3b6ea3..d00e5f0a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ Core support for aspect-interceptor, dependency injection integration, web appli * [shriek-fx](https://github.com/ElderJames/shriek-fx)   * [Util](https://github.com/dotnetcore/Util) * [Zxw.Framework.NetCore](https://github.com/VictorTzeng/Zxw.Framework.NetCore) -* [Tars.Net](https://github.com/TarsNET) * [FastCache](https://github.com/sj-distributor/FastCache) ## Contributors From ad07f086fd22cc46fa03688d60041dbe7dbd9b2c Mon Sep 17 00:00:00 2001 From: Monday Date: Sat, 31 Jan 2026 01:50:44 +0800 Subject: [PATCH 2/2] docs: add English documentation - Add 0.AOP-Introduction.en.md - AOP concept introduction - Add 1.Getting-Started.en.md - Getting started guide with examples - Add injector.en.md - IoC container and dependency injection documentation - Add reflection-extensions.en.md - Reflection extensions performance guide - Add reflection.en.md - Reflection overview Closes #250 --- docs/0.AOP-Introduction.en.md | 99 +++++++++++++ docs/1.Getting-Started.en.md | 241 +++++++++++++++++++++++++++++++ docs/injector.en.md | 217 ++++++++++++++++++++++++++++ docs/reflection-extensions.en.md | 93 ++++++++++++ docs/reflection.en.md | 1 + 5 files changed, 651 insertions(+) create mode 100644 docs/0.AOP-Introduction.en.md create mode 100644 docs/1.Getting-Started.en.md create mode 100644 docs/injector.en.md create mode 100644 docs/reflection-extensions.en.md create mode 100644 docs/reflection.en.md diff --git a/docs/0.AOP-Introduction.en.md b/docs/0.AOP-Introduction.en.md new file mode 100644 index 00000000..8acdce91 --- /dev/null +++ b/docs/0.AOP-Introduction.en.md @@ -0,0 +1,99 @@ +# 0. AOP Introduction + +## AOP Concept + +Aspect-Oriented Programming (AOP) - We know that the characteristics of Object-Oriented Programming are inheritance, polymorphism, and encapsulation. Encapsulation requires distributing functionality across different objects, which is often called responsibility allocation in software design. In other words, we design different methods for different classes. This spreads the code across individual classes. The benefit is reducing code complexity and making classes reusable. + +However, people have also discovered that while dispersing code, it also increases code duplication. What does this mean? For example, we might need to log in every method in two classes. According to OOP design principles, we must add logging content to the methods in both classes. Perhaps they are identical, but because OOP design prevents classes from connecting with each other, we cannot consolidate this duplicated code. + +Maybe someone would say: that's easy, we can write this code in an independent class with an independent method, and then call it in both classes. However, this would couple these two classes with the independent class mentioned above, and changes to it would affect both classes. So, is there any way that allows us to add code arbitrarily when needed? The programming idea of dynamically inserting code into specified methods of specified classes at runtime is called Aspect-Oriented Programming. + +Generally speaking, we call the code fragment that cuts into specified classes and specified methods an "aspect", while which classes and methods to cut into is called the "pointcut". With AOP, we can extract common code from several classes into a slice, and insert it into objects when needed, thus changing their original behavior. From this perspective, AOP is actually just a supplement to OOP. OOP distinguishes classes horizontally, while AOP adds specific code vertically to objects. With AOP, OOP becomes three-dimensional. Adding the time dimension, AOP transforms OOP from 2D to 3D, from a plane to a solid. Technically speaking, AOP is basically implemented through proxy mechanisms. + +AOP has been a milestone in programming history and is a very beneficial supplement to OOP programming. + +This section is adapted from Zhihu - Discussion on AOP concept: https://www.zhihu.com/question/24863332 + +For those who want to understand AOP in detail, you can read the above [Zhihu link](https://www.zhihu.com/question/24863332) + +Or https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/ + +Or https://www.cnblogs.com/DjlNet/p/7603654.html + +Apologies for being lazy here, I won't provide a detailed overview. + +## AOP Implementation Methods + +AOP implementations will use some common methods: + +* Using a preprocessor (like preprocessor in C++) to add source code. +* Using a post-processor to add instructions on compiled binary code. +* Using a special compiler to add code at compile time. +* Using code interceptors at runtime to intercept execution and add the required code. + +However, the most common are post-processing and code interception: + +* Post-processing, or **Static Weaving** + + Refers to using commands provided by the AOP framework to compile, thus generating AOP proxy classes at the compilation stage, also known as compile-time enhancement or static weaving. + + In .NET, this is typically done by intercepting the compilation process through custom Build Tasks in MSBuild, inserting your own IL into the generated assembly. + + .NET framework representative: [PostSharp](https://www.postsharp.net/aop.net) + +* Code interception, or **Dynamic Proxy**, **Dynamic Weaving**, **Code Hijacking** + + AOP dynamic proxy classes are generated "temporarily" in memory at runtime, thus also known as runtime enhancement or dynamic proxy. + + In .NET, this is typically done at runtime through Emit technology to generate dynamic assemblies and dynamic proxy types to intercept target methods. + + .NET framework representative: [Castle DynamicProxy](https://github.com/castleproject/Core/blob/master/docs/dynamicproxy-introduction.md) and [AspectCore](https://github.com/dotnetcore/AspectCore-Framework) + +## Brief Overview of AspectCore's Dynamic Weaving + +Simply put, dynamic weaving is creating some `proxy classes` of our real business implementation classes at runtime. + +Through the `proxy class`, some interceptor classes invisible to business logic code are called at runtime. + +Borrowing an example from Castle DynamicProxy to illustrate: + +``` csharp +public class Interceptor : IInterceptor +{ + public void Intercept(IInvocation invocation) + { + Console.WriteLine("Before target call"); + try + { + invocation.Proceed(); + } + catch(Exception) + { + Console.WriteLine("Target threw an exception!"); + throw; + } + finally + { + Console.WriteLine("After target call"); + } + } +} +``` + +This is an interceptor class. + +The purpose of dynamic weaving is to call the `Interceptor` to "intercept" `IInvocation` through the `proxy class` without `IInvocation` knowing about this interceptor class. + +The example below shows the invocation: + +![](https://github.com/castleproject/Core/raw/master/docs/images/proxy-pipeline.png) + +The blue area is the proxy class area. To the outside world, it looks like the proxied object. + +But when actually called, like the yellow arrow, it goes through layer after layer of interceptor classes before finally calling the proxied object. + +The return also follows the green arrow, going through layer after layer of interceptor classes. + +Finally achieving the purpose of dynamic proxy. + +Currently, AspectCore uses dynamic proxy as the AOP implementation rather than theoretically more performant static weaving, because I believe dynamic proxy allows for better IoC integration and can access more runtime metadata information in aspects. After continuous optimization, the performance of AspectCore dynamic proxy is no longer inferior to static weaving implementations. diff --git a/docs/1.Getting-Started.en.md b/docs/1.Getting-Started.en.md new file mode 100644 index 00000000..ce4114bb --- /dev/null +++ b/docs/1.Getting-Started.en.md @@ -0,0 +1,241 @@ +# 1. Getting Started Guide + +Version 2.0.0 :arrow_up: (Simple code example: https://github.com/cdcd72/NetCore.AspectCore.AOP.Demo) +Version 2.0.0 :arrow_down: (Simple code example: https://github.com/fs7744/AspectCoreDemo) + +## Getting Started with AspectCore + +* Launch Visual Studio. From the File menu, select New > Project. Select the ASP.NET Core Web Application project template and create a new ASP.NET Core Web Application project. + +* Install `AspectCore.Extensions.DependencyInjection` package from Nuget: + ``` + PM> Install-Package AspectCore.Extensions.DependencyInjection + ``` +* Generally, you can customize an attribute class using the abstract `AbstractInterceptorAttribute` which implements the `IInterceptor` interface. AspectCore implements attribute-based interceptor configuration by default. Your custom interceptor looks like this: + ``` csharp + public class CustomInterceptorAttribute : AbstractInterceptorAttribute + { + public async override Task Invoke(AspectContext context, AspectDelegate next) + { + try + { + Console.WriteLine("Before service call"); + await next(context); + } + catch (Exception) + { + Console.WriteLine("Service threw an exception!"); + throw; + } + finally + { + Console.WriteLine("After service call"); + } + } + } + ``` +* Define the `ICustomService` interface and its implementation `CustomService`: + ``` csharp + public interface ICustomService + { + [CustomInterceptor] + void Call(); + } + + public class CustomService : ICustomService + { + public void Call() + { + Console.WriteLine("service calling..."); + } + } + ``` +* Inject `ICustomService` in `HomeController`: + ``` csharp + public class HomeController : Controller + { + private readonly ICustomService _service; + public HomeController(ICustomService service) + { + _service = service; + } + + public IActionResult Index() + { + _service.Call(); + return View(); + } + } + ``` +* Register `ICustomService`, then configure the container to create proxy types in `ConfigureServices`: + ``` csharp + public void ConfigureServices(IServiceCollection services) + { + services.AddTransient(); + services.AddMvc(); + services.ConfigureDynamicProxy(); + } + ``` +* Finally, add `UseServiceProviderFactory(new DynamicProxyServiceProviderFactory())` in `Program`'s `CreateHostBuilder`: + ``` csharp + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + // ... + .UseServiceProviderFactory(new DynamicProxyServiceProviderFactory()); + ``` + +--- +## Interceptor Configuration +* Global Interceptors. Use the `ConfigureDynamicProxy(Action)` overload method, where `IAspectConfiguration` provides `Interceptors` to register global interceptors: + ``` csharp + services.ConfigureDynamicProxy(config => + { + config.Interceptors.AddTyped(); + }); + ``` + Global interceptor with constructor parameters. Add a parameterized constructor in `CustomInterceptorAttribute`: + ``` csharp + public class CustomInterceptorAttribute : AbstractInterceptorAttribute + { + private readonly string _name; + public CustomInterceptorAttribute(string name) + { + _name = name; + } + public async override Task Invoke(AspectContext context, AspectDelegate next) + { + try + { + Console.WriteLine("Before service call"); + await next(context); + } + catch (Exception) + { + Console.WriteLine("Service threw an exception!"); + throw; + } + finally + { + Console.WriteLine("After service call"); + } + } + } + ``` + Modify global interceptor registration: + ``` csharp + services.ConfigureDynamicProxy(config => + { + config.Interceptors.AddTyped(args: new object[] { "custom" }); + }); + ``` + Global interceptor as a service. Add in `ConfigureServices`: + ``` csharp + services.AddTransient(provider => new CustomInterceptorAttribute("custom")); + ``` + Modify global interceptor registration: + ``` csharp + services.ConfigureDynamicProxy(config => + { + // Add registered service + config.Interceptors.AddServiced(); + }); + ``` + Global interceptor acting on specific `Service` or `Method`. The following code demonstrates a global interceptor acting on classes with the `Service` suffix: + ``` csharp + services.ConfigureDynamicProxy(config => + { + config.Interceptors.AddTyped(method => method.Name.EndsWith("MethodName")); + }); + ``` + Specific global interceptor using wildcards: + ``` csharp + services.ConfigureDynamicProxy(config => + { + config.Interceptors.AddTyped(Predicates.ForService("*Service")); + }); + ``` +* In AspectCore, `NonAspectAttribute` is provided to prevent `Service` or `Method` from being proxied: + ``` csharp + [NonAspect] + public interface ICustomService + { + void Call(); + } + ``` + Also supports global ignore configuration, and wildcards: + ``` csharp + services.ConfigureDynamicProxy(config => + { + // Services under the App1 namespace will not be proxied + config.NonAspectPredicates.AddNamespace("App1"); + + // Services under namespaces ending in App1 will not be proxied + config.NonAspectPredicates.AddNamespace("*.App1"); + + // ICustomService interface will not be proxied + config.NonAspectPredicates.AddService("ICustomService"); + + // Interfaces and classes with Service suffix will not be proxied + config.NonAspectPredicates.AddService("*Service"); + + // Methods named Query will not be proxied + config.NonAspectPredicates.AddMethod("Query"); + + // Methods with Query suffix will not be proxied + config.NonAspectPredicates.AddMethod("*Query"); + }); + ``` +* Dependency Injection in Interceptors. Supports property injection, constructor injection, and service locator pattern in interceptors. + + Property injection. Mark properties with `public get and set` access in the interceptor with the `[AspectCore.DependencyInjection.FromServiceContextAttribute]` attribute to automatically inject the property, for example: + ``` csharp + public class CustomInterceptorAttribute : AbstractInterceptorAttribute + { + // Note: Property injection only works when using config.Interceptors.AddTyped(); + // It cannot be used with services.AddSingleton() + services.ConfigureDynamicProxy(config => { config.Interceptors.AddServiced(); }); + [FromServiceContext] + public ILogger Logger { get; set; } + + + public override Task Invoke(AspectContext context, AspectDelegate next) + { + Logger.LogInformation("call interceptor"); + return next(context); + } + } + ``` + Constructor injection requires making the interceptor a `Service`. Besides global interceptors, you can still use `ServiceInterceptor` to activate the interceptor from DI: + + ``` csharp + public class CustomInterceptorAttribute : AbstractInterceptorAttribute + { + private readonly ILogger ctorlogger; + + // Note: When globally configured with config.Interceptors.AddTyped(); constructor injection cannot be automatically injected, needs manual handling + // Constructor injection only works automatically when using services.AddSingleton(); + services.ConfigureDynamicProxy(config => { config.Interceptors.AddServiced(); }); + public CustomInterceptor(ILogger ctorlogger) + { + this.ctorlogger = ctorlogger; + } + } + ``` + + Service locator pattern. The interceptor context `AspectContext` can get the current scoped `ServiceProvider`: + ``` csharp + public class CustomInterceptorAttribute : AbstractInterceptorAttribute + { + public override Task Invoke(AspectContext context, AspectDelegate next) + { + var logger = context.ServiceProvider.GetService>(); + logger.LogInformation("call interceptor"); + return next(context); + } + } + ``` + +Version 2.0.0 :arrow_up: ps: Original article (https://www.thinkinmd.com/post/2020/03/20/use-aspectcore-to-implement-aop-mechanism/) +Version 2.0.0 :arrow_down: ps: Original article (http://www.cnblogs.com/liuhaoyang/p/aspectcore-introduction-1.html) diff --git a/docs/injector.en.md b/docs/injector.en.md new file mode 100644 index 00000000..802b15cd --- /dev/null +++ b/docs/injector.en.md @@ -0,0 +1,217 @@ +# IoC Container and Dependency Injection in AspectCore + +The IoC pattern and dependency injection have been a very popular pattern in recent years, and everyone should be familiar with it. ASP.NET Core provides dependency injection as built-in infrastructure. For readers still unfamiliar with dependency injection, you can refer to the relevant chapter on dependency injection in the ASP.NET Core documentation we translated: [ASP.NET Core Chinese Documentation Chapter 3 Principles (10) Dependency Injection](http://www.cnblogs.com/dotNETCoreSG/p/aspnetcore-3_10-dependency-injection.html). + +Due to the importance of IoC, AspectCore provides a lightweight, high-performance IoC container `AspectCore.Injector` that can be seamlessly integrated with AOP while providing AOP features. + +## Getting Started + +`AspectCore.DependencyInjection` is built into the AspectCore.Core package. You can get it through nuget: + +``` + Install-Package AspectCore.Core -pre +``` + +## Container and Service Registration + +In AspectCore.Injector, the container is named `IServiceContext`. Use the default implementation of the container to create a container. It provides three ways to register services: type, instance, and factory: + +```csharp +IServiceContext services = new ServiceContext(); + +// Register service using type +services.AddType(); + +// Register service using instance, service lifecycle is limited to singleton +services.AddInstance(new TaskService()); + +// Register service using delegate factory +services.AddDelegate(resolver => new TaskService()); +``` + +## Service Resolution + +AspectCore.DependencyInjection resolves services through IServiceResolver: + +```csharp +// Create service resolver +IServiceResolver serviceResolver = services.Build(); + +// Resolve single service +ISampleService sampleService = serviceResolver.Resolve(); + +// Resolve single service and verify if it's null, throw exception if null +ISampleService sampleServiceRequired = serviceResolver.ResolveRequired(); + +// Resolve service collection, returns empty collection if not registered +IEnumerable sampleServices = serviceResolver.ResolveMany(); +``` + +## Dependency Injection + +AspectCore.DependencyInjection provides constructor injection and property injection: + +```csharp +public interface ISampleService +{ +} + +public class SampleService : ISampleService +{ + private readonly ISampleRepository _sampleRepository; + private readonly ILogger _logger; + + // Constructor injection + public SampleService(ISampleRepository sampleRepository, ILogger logger) + { + _sampleRepository = sampleRepository; + _logger = logger; + } +} + +public interface ISampleRepository +{ +} + +public class SampleRepository : ISampleRepository +{ + // Property injection. Property injection conditions: marked with FromServiceContext attribute and allows set. Properties meeting conditions are automatically injected + [FromServiceContext] + public ILogger Logger { get; set; } +} +``` + +## Service Lifetimes + +AspectCore.DependencyInjection provides the following lifetimes: + +* **Transient** - Transient lifetime services are created each time they are requested. This lifetime is suitable for lightweight, stateless services. + +* **Scoped** - Scoped lifetime services are created once per scope. + +* **Singleton** - Singleton lifetime services are created the first time they are resolved, and every subsequent resolution will use the same instance. If your application needs singleton behavior, it's recommended to let the service container manage the service lifecycle rather than implementing singleton pattern and managing object lifecycle in your own classes. + +## AOP Integration + +AspectCore.DynamicProxy AOP integration is enabled by default in AspectCore.Injector and can be configured through the Configure method of IServiceContainer. + +``` +services.Configure(config => +{ + config.Interceptors.AddTyped(); +}); +``` + +## Using AspectCore.Injector in ASP.NET Core + +Install the AspectCore.Extensions.DependencyInjection nuget package: + +``` + Install-Package AspectCore.Extensions.DependencyInjection -pre +``` + +Modify ConfigureServices: + +``` +public IServiceProvider ConfigureServices(IServiceCollection services) +{ + // Add your services... + + // Add services from IServiceCollection to ServiceContainer + var container = services.ToServiceContainer(); + return container.Build(); +} +``` + +Just two lines of code to use AspectCore.Injector to replace the default DependencyInjection in ASP.NET Core. + +## Performance + +Autofac is one of the more popular IoC containers in .NET/.NET Core. We use Autofac (version 4.6.2) as a performance comparison target. We compare performance from three aspects: resolving simple objects, property injection, and constructor injection. + +The Benchmark class is written as follows: + +```csharp +[AllStatisticsColumn] +[MemoryDiagnoser] +public class Benckmarks +{ + private readonly IServiceResolver serviceResolver; + private readonly IContainer container; + + public Benckmarks() + { + var containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterType().As().InstancePerDependency(); + containerBuilder.RegisterType().As().InstancePerDependency(); + containerBuilder.RegisterType().As().InstancePerDependency().PropertiesAutowired(); + containerBuilder.RegisterType().As().InstancePerDependency(); + container = containerBuilder.Build(); + + var serviceContext = new ServiceContext(); + serviceContext.AddType(Lifetime.Transient); + serviceContext.AddType(Lifetime.Transient); + serviceContext.AddType(Lifetime.Transient); + serviceContext.AddType(Lifetime.Transient); + serviceResolver = serviceContext.Build(); + } + + [Benchmark] + public object Autofac_Sample_Resolve() + { + return container.Resolve(); + } + + [Benchmark] + public object AspectCore_Sample_Resolve() + { + return serviceResolver.Resolve(); + } + + [Benchmark] + public object Autofac_PropertyInjection() + { + return container.Resolve(); + } + + [Benchmark] + public object AspectCore_PropertyInjection() + { + return serviceResolver.Resolve(); + } + + [Benchmark] + public object Autofac_ConstructorInjection() + { + return container.Resolve(); + } + + [Benchmark] + public object AspectCore_ConstructorInjection() + { + return serviceResolver.Resolve(); + } +} +``` + +Run Benchmark in Release mode: + +``` +BenchmarkDotNet=v0.10.8, OS=Windows 10 Threshold 2 (10.0.10586) +Processor=Intel Core i5-4590 CPU 3.30GHz (Haswell), ProcessorCount=4 +Frequency=3215206 Hz, Resolution=311.0221 ns, Timer=TSC +dotnet cli version=2.0.0 + [Host] : .NET Core 4.6.00001.0, 64bit RyuJIT + DefaultJob : .NET Core 4.6.00001.0, 64bit RyuJIT + + | Method | Mean | Min | Max | Op/s | Gen 0 | Allocated | + |-------------------------------- |------------:|------------:|------------:|-------------:|-------:|----------:| + | Autofac_Sample_Resolve | 494.83 ns | 482.52 ns | 506.58 ns | 2,020,908.9 | 0.2384 | 752 B | + | AspectCore_Sample_Resolve | 88.52 ns | 87.92 ns | 89.31 ns | 11,296,837.3 | 0.0279 | 88 B | + | Autofac_PropertyInjection | 2,014.46 ns | 2,004.18 ns | 2,028.83 ns | 496,411.0 | 0.5875 | 1856 B | + | AspectCore_PropertyInjection | 307.55 ns | 303.61 ns | 310.74 ns | 3,251,544.6 | 0.1063 | 336 B | + | Autofac_ConstructorInjection | 1,465.71 ns | 1,454.43 ns | 480.38 ns | 682,263.5 | 0.6084 | 1920 B | + | AspectCore_ConstructorInjection | 284.94 ns | 283.55 ns | 286.05 ns | 3,509,500.8 | 0.0987 | 312 B | +``` + +Sample: [IoC-Sample](https://github.com/AspectCore/IoC-Sample) diff --git a/docs/reflection-extensions.en.md b/docs/reflection-extensions.en.md new file mode 100644 index 00000000..396e47ee --- /dev/null +++ b/docs/reflection-extensions.en.md @@ -0,0 +1,93 @@ +## Getting AspectCore.Extension.Reflection +Get AspectCore.Extension.Reflection through nuget: +``` + Install-Package AspectCore.Extensions.Reflection -pre +``` + +## Constructor Reflection Extensions +Provides ConstructorReflector as the entry point for constructor reflection extensions. Usage is similar to System.Reflection.ConstructorInfo: +``` +var constructorInfo = typeof(ConstructorFakes).GetTypeInfo().GetConstructor(new Type[0]); +var reflector = constructorInfo.GetReflector(); +var instance = reflector.Invoke(args); +``` +Performance test (Reflection is .NET Core provided reflection call, Reflector is AspectCore.Extension.Reflection call, Native is hardcoded call, same below): +``` + | Method | Mean | Error | StdDev | StdErr | Op/s | Gen 0 | Allocated | + |----------- |-----------:|----------:|----------:|----------:|--------------:|-------:|----------:| + | Reflection | 119.505 ns | 0.5146 ns | 0.4814 ns | 0.1243 ns | 8,367,831.8 | 0.0074 | 24 B | + | Reflector | 8.990 ns | 0.0403 ns | 0.0377 ns | 0.0097 ns | 111,236,649.9 | 0.0076 | 24 B | + | Native | 3.825 ns | 0.0620 ns | 0.0580 ns | 0.0150 ns | 261,404,148.5 | 0.0076 | 24 B | +``` + +## Method Invocation Reflection Extensions +Provides MethodReflector as the entry point for method reflection extensions. Usage is similar to System.Reflection.MethodInfo: +``` +var typeInfo = typeof(MethodFakes).GetTypeInfo(); +var method = typeInfo.GetMethod("Call"); +var refector = method.GetReflector(); +refector.Invoke(instance,args); +``` +Performance test: +``` + | Method | Mean | Error | StdDev | StdErr | Op/s | + |------------------- |------------:|----------:|----------:|----------:|----------------:| + | Native_Call | 1.0473 ns | 0.0064 ns | 0.0050 ns | 0.0015 ns | 954,874,046.8 | + | Reflection_Call | 91.9543 ns | 0.3540 ns | 0.3311 ns | 0.0855 ns | 10,874,961.4 | + | Reflector_Call | 7.1544 ns | 0.0628 ns | 0.0587 ns | 0.0152 ns | 139,774,408.3 | + ``` + +## Property Invocation Reflection Extensions +Provides PropertyReflector as the entry point for property reflection extensions. Usage is similar to System.Reflection.PropertyInfo: +``` +var property = typeof(PropertyFakes).GetTypeInfo().GetProperty("Property"); +var reflector = property.GetReflector(); +var value = reflector.GetValue(instance); +``` +Performance test: +``` + | Method | Mean | Error | StdDev | StdErr | Op/s | Gen 0 | Allocated | + |-------------------------- |-----------:|----------:|----------:|----------:|--------------:|-------:|----------:| + | Native_Get_Property | 1.178 ns | 0.0244 ns | 0.0229 ns | 0.0059 ns | 848,858,716.1 | - | 0 B | + | Reflection_Get_Property | 103.028 ns | 0.2217 ns | 0.2074 ns | 0.0535 ns | 9,706,088.1 | | 0 B | + | Reflector_Get_Property | 4.172 ns | 0.0194 ns | 0.0172 ns | 0.0046 ns | 239,694,827.7 | - | 0 B | + | Native_Set_Property | 2.002 ns | 0.0122 ns | 0.0114 ns | 0.0030 ns | 499,447,543.5 | - | 0 B | + | Reflection_Set_Property | 188.313 ns | 0.5347 ns | 0.5002 ns | 0.1292 ns | 5,310,298.0 | 0.0203 | 64 B | + | Reflector_Set_Property | 5.878 ns | 0.0234 ns | 0.0219 ns | 0.0056 ns | 170,138,324.7 | - | 0 B | + ``` + +## Attribute Retrieval Extensions +Taking retrieving attributes marked on a method as an example. + +Method definition: +``` +[Attribute1] +[Attribute2("benchmark", Id = 10000)] +[Attribute3] +[Attribute3] +[Attribute3] +public void Method() +{ +} +``` +Use MethodReflector to get attributes: +``` +var method = type.GetMethod("Method"); +var reflector = method.GetReflector(); +var attribute1 = reflector.GetCustomAttribute(typeof(Attribute1)); +var attributes = reflector.GetCustomAttributes(); +``` +Performance test: +``` + | Method | Mean | Op/s | Gen 0 | Allocated | + |-------------------------------------------- |------------:|-------------:|-------:|----------:| + | Reflection_GetCustomAttribute | 4,642.13 ns | 215,418.5 | 0.2289 | 744 B | + | Reflector_GetCustomAttribute | 35.52 ns | 28,154,302.3 | 0.0101 | 32 B | + | Reflection_GetCustomAttributes_WithAttrType | 5,354.49 ns | 186,759.2 | 0.3281 | 1048 B | + | Reflector_GetCustomAttributes_WithAttrType | 168.61 ns | 5,930,816.1 | 0.0710 | 224 B | + | Reflection_GetCustomAttributes_All | 7,915.45 ns | 126,335.2 | 0.5035 | 1632 B | + | Reflector_GetCustomAttributes_All | 98.36 ns | 10,166,253.6 | 0.0737 | 232 B | + | Reflection_IsDefined | 1,723.30 ns | 580,283.6 | 0.0801 | 256 B | + | Reflector_IsDefined | 35.55 ns | 28,126,759.1 | - | 0 B | +``` +You can see that AspectCore.Extension.Reflection has 2 orders of magnitude optimization in performance compared to reflection, achieving the same order of magnitude as hardcoded calls. The optimization for attribute retrieval is particularly obvious. diff --git a/docs/reflection.en.md b/docs/reflection.en.md new file mode 100644 index 00000000..0739ed32 --- /dev/null +++ b/docs/reflection.en.md @@ -0,0 +1 @@ +In the process of implementing AOP from scratch, it's inevitable to need extensive reflection-related operations. Although reflection performance has been greatly optimized in .NET 4.5+/.NET Core, to pursue ultimate performance, I implemented some reflection alternatives myself, including constructor invocation, method invocation, field read/write, property read/write, and attribute reading. During refactoring, I encapsulated the reflection extension operations into a separate project to facilitate self-use and use by others. [Get AspectCore.Extension.Reflection](https://github.com/dotnetcore/AspectCore-Framework/blob/master/docs/reflection-extensions.md)