SoFunction
Updated on 2025-04-09

The most detailed solution for .NET non-invasive object pooling

Pooling(/inversionhourglass/Pooling), compile time object pool component, the type of the specifiednewThe operation is replaced by an object pool operation, simplifying the encoding process without the need for developers to manually write object pool operation code. It also provides a completely intrusive solution that can be used as a temporary performance optimization solution and a Laojiu project performance optimization solution.

Start quickly

Quote

dotnet add package

make surePooling is configured in the file if the current project does notFiles can be compiled directly and will be automatically generateddocument:

<Weavers xmlns:xsi="http:///2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="">
  <Pooling /> <!--Make sure to existPoolingnode-->
</Weavers>
// 1. IPoolItem interface is implemented by the pooled typepublic class TestItem : IPoolItem
{
    public int Value { get; set; }
    // When the object returns to object pooling, reset the instance state through this method    public bool TryReset()
    {
        return true;
    }
}
// 2. Create an object of this type anywhere using the new keywordpublic class Test
{
    public void M()
    {
        var random = new Random();
        var item = new TestItem();
         = ();
        ();
    }
}
// Compiled codepublic class Test
{
    public void M()
    {
        TestItem item = null;
        try
        {
            var random = new Random();
            item = Pool<TestItem>.Get();
             = ();
            ();
        }
        finally
        {
            if (item != null)
            {
                Pool<TestItem>.Return(item);
            }
        }
    }
}

IPoolItem

As shown in the code in Quick Start, the implementationIPoolItemThe type of the interface is a pooled type. At compile time, Pooling will replace its new operation with an object pool operation and return the pooled object instance to the object pool in the finally block.IPoolItemOnly oneTryResetMethod, This method is used to reset the state when the object returns to the object pool. When this method returns false, it means that the state reset failed, and the object will be discarded.

PoolingExclusiveAttribute

By default, the implementationIPoolItemThe pooling type will be pooled in all methods, but sometimes we may want the pooling type not to pool in some types. For example, we may create some management types of pooling types or Builder types, and then apply them on the pooling type.PoolingExclusiveAttributeThis allows you to specify that the pooling type is not pooled in some types/methods.

[PoolingExclusive(Types = [typeof(TestItemBuilder)], Pattern = "execution(* TestItemManager.*(..))")]
public class TestItem : IPoolItem
{
    public bool TryReset() => true;
}
public class TestItemBuilder
{
    private readonly TestItem _item;
    private TestItemBuilder()
    {
        // Since TestItemBuilder is excluded through the PoolingExclusive Types property, this will not be replaced with an object pool operation.        _item = new TestItem();
    }
    public static TestItemBuilder Create() => new TestItemBuilder();
    public TestItemBuilder SetXxx()
    {
        // ...
        return this;
    }
    public TestItem Build()
    {
        return _item;
    }
}
public class TestItemManager
{
    private TestItem? _cacheItem;
    public void Execute()
    {
        // Since all methods under TestItemManager are excluded through the PoolingExclusive Pattern property, this will not be replaced with object pool operation.        var item = _cacheItem ?? new TestItem();
        // ...
    }
}

As shown in the above code,PoolingExclusiveAttributeThere are two attributesTypesandPatternTypesforTypeType array, the current pooling type will not perform pooling operations in the type method in the array;PatternforstringType AspectN expressions can be matched carefully to specific methods (see details of the AspectN expression format:/inversionhourglass//blob/master/), the current pooling type will not be pooled in the matched method. Both properties can be used or used at the same time. When used at the same time, all types/methods matched by the two properties will be excluded.

NonPooledAttribute

As mentioned earlier, you can passPoolingExclusiveAttributeSpecifies that the current pooled object does not perform pooling operations in some types/methods, but becausePoolingExclusiveAttributeIt needs to be directly applied to the pooling type, so if you use the pooling type in the third-party class library, you cannot directly transfer itPoolingExclusiveAttributeApplied to this pooling type. For this kind of situation, you can useNonPooledAttributeIndicates that the current method does not perform pooling operations.

public class TestItem1 : IPoolItem
{
    public bool TryReset() => true;
}
public class TestItem2 : IPoolItem
{
    public bool TryReset() => true;
}
public class TestItem3 : IPoolItem
{
    public bool TryReset() => true;
}
public class Test
{
    [NonPooled]
    public void M()
    {
        // Since the method applies NonPooledAttribute, the following three new operations will not be replaced with object pool operations        var item1 = new TestItem1();
        var item2 = new TestItem2();
        var item3 = new TestItem3();
    }
}

Sometimes you may not want all pooling types in the method to not perform pooling operations, and you can passNonPooledAttributeTwo properties ofTypesandPatternSpecifies the pooling type that cannot be performed.TypesforTypeType array, all types in the array cannot be pooled in the current method;PatternforstringType AspectN type expression, all matching types cannot be pooled in the current method.

public class Test
{
    [NonPooled(Types = [typeof(TestItem1)], Pattern = "*..TestItem3")]
    public void M()
    {
        // TestItem1 does not allow pooling operations through Types, TestItem3 does not allow pooling operations through Pattern, only TestItem2 can do pooling operations        var item1 = new TestItem1();
        var item2 = new TestItem2();
        var item3 = new TestItem3();
    }
}

AspectN type expressions are flexible and changeable, and support logical non-operators!, so it is very convenient to use AspectN type expression to allow only one type. For example, the above example can be changed to[NonPooled(Pattern = "!TestItem2")], For more AspectN expression description, please see:/inversionhourglass//blob/master/ 。

NonPooledAttributeIt can be applied not only to method levels, but also to types and assemblies. Applying to a class is equivalent to all methods applied to a class (including attributes and constructors), applying to an assembly is equivalent to all methods applied to the current assembly (including attributes and constructors), and if it is not specified when applied to the assemblyTypesandPatternTwo properties are equivalent to disabling Pooling in the current assembly.

Non-invasive pooling operation

After reading the previous content and looking at the title, you may be muttering, "Where is this non-invasive, this is not a pure title party." Now, the title part is here. Pooling provides a non-invasive access method, suitable for temporary performance optimization and old project transformation, without implementationIPoolItemInterface, you can specify the pooling type through configuration.

Suppose there is currently the following code:

namespace ;
public class Item1
{
    public object? GetAndDelete() => null;
}
public class Item2
{
    public bool Clear() => true;
}
public class Item3 { }
public class Test
{
    public static void M1()
    {
        var item1 = new Item1();
        var item2 = new Item2();
        var item3 = new Item3();
        ($"{item1}, {item2}, {item3}");
    }
    public static async ValueTask M2()
    {
        var item1 = new Item1();
        var item2 = new Item2();
        await ();
        var item3 = new Item3();
        ($"{item1}, {item2}, {item3}");
    }
}

Projects are citingAfter that, when the project is compiled, aWe modify the file according to the following examplePoolingnode:

<Weavers xmlns:xsi="http:///2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="">
  <Pooling>
    <Items>
      <Item pattern="." />
      <Item pattern="" inspect="execution(* Test.M1(..))" />
      <Item stateless="*..Item3" not-inspect="method(* Test.M2())" />
	</Items>
  </Pooling>
</Weavers>

In the above configuration, eachItemThe node matches a pooling type. The above configuration shows all four attributes, and their meanings are:

  • pattern: AspectN type + method expression. The matching type is a pooling type, and the matching method is a state reset method (equivalent to the TryReset method of IPoolItem). It should be noted that the reset method must be parameterless.
  • stateless: AspectN type expression. The matching type is a pooled type, which is a stateless type and does not require a reset operation to return to the object pool.
  • inspect: AspectN expression.patternandstatelessThe pooling type matched will only be pooled in the method matched by the expression. When this configuration defaults to all methods matching the current assembly.
  • not-inspect: AspectN expression.patternandstatelessThe matching pooling type will not be pooled in the method matched by the expression. When this configuration defaults, it means that no methods are excluded. The final pooling type can perform pooling operations asinspectCollection andnot-inspectThe difference set of sets.

Then through the above configuration,TestThe compiled code is:

public class Test
{
    public static void M1()
    {
        Item1 item1 = null;
        Item2 item2 = null;
        Item3 item3 = null;
        try
        {
            item1 = Pool<Item1>.Get();
            item2 = Pool<Item2>.Get();
            item3 = Pool<Item3>.Get();
            ($"{item1}, {item2}, {item3}");
        }
        finally
        {
            if (item1 != null)
            {
                ();
                Pool<Item1>.Return(item1);
            }
            if (item2 != null)
            {
                if (())
                {
                    Pool<Item2>.Return(item2);
                }
            }
            if (item3 != null)
            {
                Pool<Item3>.Return(item3);
            }
        }
    }
    public static async ValueTask M2()
    {
        Item1 item1 = null;
        try
        {
            item1 = Pool<Item1>.Get();
            var item2 = new Item2();
            await ();
            var item3 = new Item3();
            ($"{item1}, {item2}, {item3}");
        }
        finally
        {
            if (item1 != null)
            {
                ();
                Pool<Item1>.Return(item1);
            }
        }
    }
}

Careful you may noticeM1In the method,item1anditem2There is a difference in the call to reset the method, this is becauseItem2The return value type of the reset method isbool, Poolinng will use its result as the basis for whether the reset is successful, forvoidOr other type of return value, Pooling will default to its reset after the method is successfully returned.

Zero-invasive pooling operation

Are you a little confused when you see this title? Just after introducing non-invasive, why did you get another zero-invasive? What is the difference between them?

In the non-invasive pooling operation introduced above, we do not need to change any C# code to complete the pooling operation of the specified type, but we still need to add NuGet dependencies and need to be modified for configuration, which still requires manual operations by the developer. So how can developers not need any operation at all? The answer is also very simple, which is to put this step into the CI process or release process. Yes, zero intrusion is targeted at developers, not really nothing is needed, but rather delaying the steps to reference NuGet and configuration into the CI/release process.

What are the advantages

Optimizations like object pools often do not only require optimization for a certain project, but this optimization may be universal. At this time, compared to the modification of one project, unified configuration in the CI process/release process is a faster choice. In addition, when facing some antique projects, no one may be willing to change any code. Even if it is just the project file and configuration file, it can be completed by modifying the CI/release process. Of course, the impact of modifying the unified CI/release process may be wider. Here we only provide a zero-invasive idea, and the specific situation needs to be comprehensively considered in combination with actual conditions.

How to implement it

The most direct way is to pass the CI construction process or release processdotnet add package Add NuGet dependencies to the project and copy the preconfigured ones to the project directory. However, if the project also references other Fody plugins, directly overwriting the original may cause the original plugin to be invalid. Of course, you can also make things more complicated by script control. Here I recommend a .NET CLI tool.Cli4FodyNuGet dependencies and configuration can be completed in one step.

<Weavers xmlns:xsi="http:///2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="">
  <Pooling>
    <Items>
      <Item pattern="." />
      <Item pattern="" inspect="execution(* Test.M1(..))" />
      <Item stateless="*..Item3" not-inspect="method(* Test.M2())" />
	</Items>
  </Pooling>
</Weavers>

The above commands corresponding to using Cli4Fody are:

fody-cli  \
        --addin Pooling -pv 0.1.0 \
            -n Items:Item -a "pattern=." \
            -n Items:Item -a "pattern=" -a "inspect=execution(* Test.M1(..))" \
            -n Items:Item -a "stateless=*..Item3" -a "not-inspect=method(* Test.M2())"

The advantage of Cli4Fody is that NuGet references and can be done simultaneously, and Cli4Fody does not modify or delete the configuration of other Fody plugins in it. For more Cli4Fody related configurations, please see:/inversionhourglass/Cli4Fody

Rougamo Zero Invasive Optimization Case

Rougamo, an AOP component woven with static code. Roujiamo has added structure support in version 2.2.0, and GC can be optimized through structure. However, the use of structures is not as convenient as class, and the parent class cannot be inherited can only implement interfaces, so manyMoAttributeThe default implementation in the struct needs to be implemented repeatedly when defining the structure. Now, you can use Pooling to optimize the GC of Roujiamo through object pools. In this example, Docker will be used to demonstrate how to complete zero-invasive pooling operations using Cli4Fody in the Docker build process:

Directory structure:

.
├── Lib
│   └──                        # Dependency│   └──                  # Inherit MoAttribute└── RougamoPoolingConsoleApp
    └── 
    └── Dockerfile
    └──   # Quote, no Fody plugin dependencies    └── 

The test project isThere are two empty test methods definedMandNBoth methods have been appliedTestAttribute. This test will use Cli4Fody to add dependencies to the project in the build step of Docker and willTestAttributeConfigure as pooling type, and set it only inPooling is performed in the method, and then compare it with BenchmarkMandNGC situation.

// TestAttribute
public class TestAttribute : MoAttribute
{
    // To make GC more obvious, each TestAttribute will hold a byte array of length 1024    private readonly byte[] _occupy = new byte[1024];
}
// BenchmarkTest
public class BenchmarkTest
{
    [Benchmark]
    [Test]
    public void M() { }
    [Benchmark]
    [Test]
    public void N() { }
}
// Program
var config = ()
    .AddDiagnoser();
var _ = &lt;BenchmarkTest&gt;(config);

Dockerfile

FROM /dotnet/sdk:8.0
WORKDIR /src
COPY . .
ENV PATH="$PATH:/root/.dotnet/tools"
RUN dotnet tool install -g Cli4Fody
RUN fody-cli  --addin Rougamo -pv 4.0.4 --addin Pooling -pv 0.1.0 -n Items:Item -a "stateless=+" -a "inspect=method(* (..))"
RUN dotnet restore
RUN dotnet publish "./RougamoPoolingConsoleApp/" -c Release -o /src/bin/publish
WORKDIR /src/bin/publish
ENTRYPOINT ["dotnet", ""]

Final by Cli4FodyInserted inTestAttributePooling operation was performed, andInserted inTestAttributeNo pooling operation was performed, and the final Benchmark result was as follows:

| Method | Mean     | Error   | StdDev   | Gen0   | Gen1   | Allocated |
|------- |---------:|--------:|---------:|-------:|-------:|----------:|
| M      | 188.7 ns | 3.81 ns |  6.67 ns | 0.0210 |      - |     264 B |
| N      | 195.5 ns | 4.09 ns | 11.74 ns | 0.1090 | 0.0002 |    1368 B |

The complete sample code is saved in:/inversionhourglass/Pooling/tree/master/samples/DockerSample

In this example, the object pool optimization of Rougamo is completed by using Cli4Fody in the build step of Docker, and the entire process is completely insensitive and zero-invasive for development. If you are planning to use this method to optimize object pooling for Rougamo, you need to pay attention to the aspect type in the current exampleTestAttributeIt is stateless, so you need to confirm with the development that all defined aspect types are stateless. For stateful aspect types, you need to define the reset method and use the pattern attribute instead of the stateless attribute when defining the Item node.

There is another point you may not notice in this example. Only the Lib project is referenced. The RougamoPoolingConsoleApp project is not referenced. By default, it is applied toBenchmarkTestofTestAttributeIt should not take effect, but it took effect in my example. This is because when using Cli4Fody, Rougamo's relevant parameters are also specified. Cli4Fody will add a reference to RougamoPoolingConsoleApp, so Cli4Fody can also be used to avoid missing the direct dependency of the Fody plugin of the project team. For more details on Cli4Fody, please see:/inversionhourglass/Cli4Fody

Configuration Items

Introduced in non-invasive pooling operationsItemsNode configuration, exceptItemsConfiguration item Pooling also provides other configuration items, and the following is a complete configuration example:

<Pooling enabled="true" composite-accessibility="false">
  <Inspects>
    <Inspect>any_aspectn_pattern</Inspect>
    <Inspect>any_aspectn_pattern</Inspect>
  </Inspects>
  <NotInspects>
    <NotInspect>any_aspectn_pattern</NotInspect>
    <NotInspect>any_aspectn_pattern</NotInspect>
  </NotInspects>
  <Items>
    <Item pattern="method_name_pattern" stateless="type_pattern" inspect="any_aspectn_pattern" not-inspect="any_aspectn_pattern" />
    <Item pattern="method_name_pattern" stateless="type_pattern" inspect="any_aspectn_pattern" not-inspect="any_aspectn_pattern" />
  </Items>
</Pooling>
Node path Attribute name use
/Pooling enabled Whether to enable Pooling
/Pooling composite-accessibility Whether AspectN uses class + method comprehensive accessibility to match. By default, only matches according to method accessibility. For example, the accessibility of the class is internal and the accessibility of the method is public. By default, the accessibility of the method is public. After setting this configuration to true, the accessibility of the method is recognized as internal.
/Pooling/Inspects/Inspect [Node value] AspectN expression.
Global filters, only methods matched by this expression will check whether the pooling type is used internally and perform pooling operations to replace it. Even if it is implementedIPoolItemThe pooling type is also limited by this configuration.
This node can be configured with multiple lines, and the matching method set is a union of multiple lines configurations.
By default, this node matches all methods of the current assembly.
The final method collection is the matching set of the node configuration and/Pooling/NotInspectsConfigure the difference set of matching sets.
/Pooling/NotInspects/NotInspect [Node value] AspectN expression.
A global filter, the internal pooling operation is not replaced by the method matched by this expression. Even if it is implementedIPoolItemThe pooling type is also limited by this configuration.
This node can be configured with multiple lines, and the matching method set is a union of multiple lines configurations.
By default, this node means that no methods are excluded.
The final collection of methods is/Pooling/InspectsThe difference between the set that configures matching and the set that matches the node configuration.
/Pooling/Items/Item pattern AspectN type + method name expression.
The matching type will be used as a pooled type, and the matching method will be used as a reset method.
The reset method must be a parameterless method, if the return value type isbool, the return value will also be used as the basis for whether the reset is successful.
This property isstatelessOnly one of the properties can be chosen.
/Pooling/Items/Item stateless AspectN type expression.
The matching type will be used as a pooled type, which is a stateless type and does not need to be reset before returning to the object pool.
This property ispatternOnly choose one of two.
/Pooling/Items/Item inspect AspectN expression.
patternandstatelessThe pooling type matched will only be pooled in the method matched by the expression.
When this configuration defaults to all methods matching the current assembly.
The method set that the current pooling type can finally be applied is the method set that matches the configuration.not-inspectConfigure the difference set of matching methods.
/Pooling/Items/Item not-inspect AspectN expression.
patternandstatelessThe matching pooling type will not be pooled in the method matched by the expression.
When this configuration defaults, it means that no methods are excluded.
The set of methods that can be applied to the current pooling type isinspectThe difference between the set of methods that match the configuration and the set of methods that match the configuration.

You can see that AspectN expressions are used extensively in the configuration. For more information about the usage of AspectN expressions, please refer to:/inversionhourglass//blob/master/

Also, it should be noted that all methods in the assembly are like memory, while AspectN is like a pointer. Be extra careful when operating memory through pointers. Matching expected types to pooled types may cause the same object instance to be used concurrently, so try to use exact matches when using AspectN expressions to avoid fuzzy matches.

Object pool configuration

Maximum number of object holdings in the object pool

The maximum number of objects held by each pooled object pool is the number of logical processors multiplied by 2 * 2, there are two ways to modify this default setting.

  • Specify by code

    passYou can set the maximum number of object holdings for all pooled types, throughPool<T>.MaximumRetainedYou can set the maximum number of object holdings for the object pool of a specified pooling type. The latter has higher priority than the former.

  • Specified by environment variable

    Specifying environment variables at the start of the application can modify the maximum number of objects held by the object pool.NET_POOLING_MAX_RETAINUsed to set the maximum number of object holdings for all pooled types,NET_POOLING_MAX_RETAIN_{PoolItemFullName}Used to set the maximum number of object holdings for the object pool of a specified pooling type, where{PoolItemFullName}It is the full name of the pooled type (namespace. class name). It should be noted that the full name needs to be.Replace with_,for exampleNET_POOLING_MAX_RETAIN_System_Text_StringBuilder. The priority of environment variables is higher than that of code. It is recommended to use environment variables for control, which is more flexible.

Custom object pool

We know that there is an object pool libraryPooling did not directly refer to this class library and chose to build a self-built object pool because Pooling, as a compile-time component, calls to methods are directly weaved through IL. If the three-party class library is referenced and the three-party class library has modified the method signature in subsequent updates, it may be thrown at runtime.MethodNotFoundException, so minimizing three-party dependencies is the best choice for compile-time components.

Some friends may be worried about the performance of self-built object pools. You can rest assured that the implementation of Pooling object pools is fromCopy it, and streamline itObjectPoolProviderPooledObjectPolicyetc., maintain the most streamlined default object pool implementation. At the same time, Pooling supports custom object pools to implementIPoolInterface definition of a common object pool, implementingIPool<T>The interface defines an object pool of a specific pooling type. The following is a brief demonstration of how to replace the object pool with a custom object pool

// General object poolpublic class MicrosoftPool : IPool
{
    private static readonly ConcurrentDictionary&lt;Type, object&gt; _Pools = [];
    public T Get&lt;T&gt;() where T : class, new()
    {
        return GetPool&lt;T&gt;().Get();
    }
    public void Return&lt;T&gt;(T value) where T : class, new()
    {
        GetPool&lt;T&gt;().Return(value);
    }
    private ObjectPool&lt;T&gt; GetPool&lt;T&gt;() where T : class, new()
    {
        return (ObjectPool&lt;T&gt;)_Pools.GetOrAdd(typeof(T), t =&gt;
        {
            var provider = new DefaultObjectPoolProvider();
            var policy = new DefaultPooledObjectPolicy&lt;T&gt;();
            return (policy);
        });
    }
}
// Object pooling of specific pooling typespublic class SpecificalMicrosoftPool&lt;T&gt; : IPool&lt;T&gt; where T : class, new()
{
    private readonly ObjectPool&lt;T&gt; _pool;
    public SpecificalMicrosoftPool()
    {
        var provider = new DefaultObjectPoolProvider();
        var policy = new DefaultPooledObjectPolicy&lt;T&gt;();
        _pool = (policy);
    }
    public T Get()
    {
        return _pool.Get();
    }
    public void Return(T value)
    {
        _pool.Return(value);
    }
}
// The replacement operation is best completed directly in the Main entrance. Once the object pool is used, it will no longer run for the replacement operation.// Replace the general object pool implementation(new MicrosoftPool());
// Replace a specific type of object poolPool&lt;Xyz&gt;.Set(new SpecificalMicrosoftPool&lt;Xyz&gt;());

Not just used as object pool

Although Pooling's intention is to simplify object pooling operations and non-invasive project transformation optimization, thanks to the implementation of Pooling and the customized object pooling capabilities provided, what you can do with Pooling is not just an object pool. The implementation of Pooling is equivalent to burying a probe in all parametersless construct method calls. You can do anything here. Here are a few examples.

Single case

// Define a singleton object poolpublic class SingletonPool&lt;T&gt; : IPool&lt;T&gt; where T : class, new()
{
    private readonly T _value = new();
    public T Get() =&gt; _value;
    public void Return(T value) { }
}
// Replace object pool implementationPool&lt;ConcurrentDictionary&lt;Type, object&gt;&gt;.Set(new SingletonPool&lt;ConcurrentDictionary&lt;Type, object&gt;&gt;());
// Set ConcurrentDictionary<Type, object> to pooling type through configuration// &lt;Item stateless="&amp;lt;, object&amp;gt;" /&gt;

Through the above changes, you successfully make allConcurrentDictionary<Type, object>>Share an instance.

Control the signal quantity

// Define semaphore object poolpublic class SemaphorePool&lt;T&gt; : IPool&lt;T&gt; where T : class, new()
{
    private readonly Semaphore _semaphore = new(3, 3);
    private readonly DefaultPool&lt;T&gt; _pool = new();
    public T Get()
    {
        if (!_semaphore.WaitOne(100)) return null;
        return _pool.Get();
    }
    public void Return(T value)
    {
        _pool.Return(value);
        _semaphore.Release();
    }
}
// Replace object pool implementationPool&lt;Connection&gt;.Set(new SemaphorePool&lt;Connection&gt;());
// Set Connection to pooling type by configuration// &lt;Item stateless="" /&gt;

Use semaphore object pool control in this exampleConnectionThe number of flow restriction scenarios is very suitable for some flow restriction scenarios.

Thread singleton

// Define a pool of ready-made singleton objectspublic class ThreadLocalPool&lt;T&gt; : IPool&lt;T&gt; where T : class, new()
{
    private readonly ThreadLocal&lt;T&gt; _random = new(() =&gt; new());
    public T Get() =&gt; _random.Value!;
    public void Return(T value) { }
}
// Replace object pool implementationPool&lt;Random&gt;.Set(new ThreadLocalPool&lt;Random&gt;());
// Set Connection to pooling type by configuration// &lt;Item stateless="" /&gt;

When you want to reduce GC pressure through singletons but the object is not thread-safe, you canThreadLocalImplement in-thread singleton.

Additional initialization

// Define the current attribute injection object poolpublic class ServiceSetupPool : IPool&lt;Service1&gt;
{
    public Service1 Get()
    {
        var service1 = new Service1();
        var service2 = ?.GetService&lt;Service2&gt;();
        service1.Service2 = service2;
        return service1;
    }
    public void Return(Service1 value) { }
}
// Define pooling typepublic class Service2 { }
[PoolingExclusive(Types = [typeof(ServiceSetupPool)])]
public class Service1 : IPoolItem
{
    public Service2? Service2 { get; set; }
    public bool TryReset() =&gt; true;
}
// Replace object pool implementationPool&lt;Service1&gt;.Set(new ServiceSetupPool());

Use Pooling in this exampleComplete attribute injection, and other initialization operations can be completed in the same way.

Use your imagination

The previous examples may not be practical. The main purpose of these examples is to inspire everyone to open up ideas and understand the basic implementation principle of Pooling is to replace the new operation of temporary variables with object pool operations, and to understand the scalability of custom object pools. Maybe you can't use Pooling now, but in a certain demand scenario in the future, you may be able to use Pooling to implement it quickly without a lot of code changes.

Things to note

Do not perform multiplexing initialization operations in the pooling type constructor

Objects obtained from the object pool may be multiplexed objects, and the multiplexed objects will not execute the constructor again, so if you have some initialization operations that you want to execute every time you reuse, then you should separate the operation into a method and call it after the new operation instead of putting it in the constructor method

// Modify the definition of the pooled objectpublic class Connection : IPoolItem
{
    private readonly Socket _socket;
    public Connection()
    {
        _socket = new(, , );
        // Connect should not be connected here. Connect operation should be made into a method separately, and then called after the new operation.        _socket.Connect("127.0.0.1", 8888);
    }
    public void Write(string message)
    {
        // ...
    }
    public bool TryReset()
    {
        _socket.Disconnect(true);
        return true;
    }
}
// Use the pooled object before modificationvar connection = new Connection();
("message");
// Modified pooled object definitionpublic class Connection : IPoolItem
{
    private readonly Socket _socket;
    public Connection()
    {
        _socket = new(, , );
    }
    public void Connect()
    {
        _socket.Connect("127.0.0.1", 8888);
    }
    public void Write(string message)
    {
        // ...
    }
    public bool TryReset()
    {
        _socket.Disconnect(true);
        return true;
    }
}
// Use the pooled object after modificationvar connection = new Connection();
();
("message");

Only replace new operations with object pool operations with parameterless construction methods

Since the multiplexed object cannot execute the constructor again, the constructor parameters are meaningless to the pooled object. If you want to complete some initialization operations by constructing parameters, you can create a new initialization method to receive these parameters and complete the initialization, or receive these parameters through attributes.

Pooling will check whether the new operation calls the parameterless constructor method. If the parameterless constructor is called, the new operation will not be replaced with the object pool operation.

Be careful not to persist pooled type instances

Pooling's object pooling operation is method-level, that is, the pooled object is created in the current method and is also released at the end of the current method. The pooled object cannot be persisted into the field, otherwise there will be a risk of concurrent use. If the declaration cycle of a pooled object spans multiple methods, you should create the object pool manually and manage the object manually.

Pooling will conduct a simple persistent check during compilation, and the pooled objects that are found will not be pooled. But it should be noted that this kind of troubleshooting can only troubleshoot some simple persistence operations, and cannot troubleshoot persistence operations in complex situations. For example, if you call another method in the current method, pass in the pooled object instance, and then perform persistence operations in the called method. So you need to pay attention to it yourself to avoid persisting pooled objects.

Assemblies that require object pooling operations to be replaced during compilation need to be referenced

The principle of Pooling is to check the MSIL of all methods (or some methods can be selected through configuration) at compile time, and troubleshoot all newobj operations to complete the object pool replacement operation. The triggering of this operation is completed by adding an MSBuild task through Fody. Only when the current assembly directly references Fody can the operation of adding MSBuild tasks be completed. Some configurations allow direct reference to add MSBuild tasks to be completed.

Things to note when using multiple Fody plugins at the same time

When a project references a Fody plugin, a automatically generated at compile timeFile, if inIf the file already exists, refer to another Fody plug-in. When compiled, the new plug-in will not be appended toIn the file, it needs to be configured manually. When referencing multiple Fody plugins at the same time, you need to pay attention to them.order inThe order corresponds to the order of plug-in execution. Some Fody plug-ins may have cross-functional functions, and different orders may produce different effects.

AspectN

At the end of the article, I mentioned AspectN again. I have always called it AspectJ-Like expression because it was indeed designed with reference to the format of AspectJ expression, but it is not a solution to keep calling it like this. Now, according to convention, it is renamed to AspectN expression (I searched and found that there is no noun in .NET, so there should be no conflict). AspectN first originated in Roujiamo 2.0, used to provide more accurate point-cutting matching, and is now being put into use in Pooling again.

When using Fody or directly using the development of MSBuild task plugin, how to find the type or method that needs to be modified is always the top priority. The most common way is to locate the Attribute metadata on the type and method, but doing so basically determines that you must modify the code to add the Attribute application, which is invasive. AspectN provides a non-invasive type and method matching mechanism, and the infinite information that strings can carry gives AspectN infinite refined matching possibilities. Many Fody plug-ins can use AspectN to achieve intrusive code weaving. For example, you can use AspectN to implement the configuration to specify which types or methods need to be used and which do not.

AspectN does not depend on Fody, only depends on, if you are using Fody or maybe you can try AspectN(/inversionhourglass/). AspectN is a shared project (Shared Project). No NuGet is released and no specific version is dependent on. Using AspectN, you need to clone AspectN locally as a shared project to directly reference. If your project is managed using git, it is recommended to add AspectN as a submodule to your repository (refer toRougamoandPooling)。

This is all about this article about the .NET non-invasive object pool solution. For more related .NET non-invasive object pool content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!