<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexey Popov</title>
    <description>The latest articles on DEV Community by Alexey Popov (@alexey_popov).</description>
    <link>https://dev.to/alexey_popov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4083826%2Fd51b5f95-52ad-4fed-9ca7-9342077b55e6.jpg</url>
      <title>DEV Community: Alexey Popov</title>
      <link>https://dev.to/alexey_popov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexey_popov"/>
    <language>en</language>
    <item>
      <title>A single allocation is cheap. Millions of them can turn into latency spikes.

This article explores how real .NET 10 and ASP.NET Core 10 APIs use stackalloc, Span&lt;T&gt;, Memory&lt;T&gt;, and object pools to reduce unnecessary allocations in high-throughput systems.</title>
      <dc:creator>Alexey Popov</dc:creator>
      <pubDate>Wed, 16 Sep 2026 15:22:59 +0000</pubDate>
      <link>https://dev.to/alexey_popov/a-single-allocation-is-cheap-millions-of-them-can-turn-into-latency-spikes-this-article-1flk</link>
      <guid>https://dev.to/alexey_popov/a-single-allocation-is-cheap-millions-of-them-can-turn-into-latency-spikes-this-article-1flk</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/alexey_popov/high-performance-net-reducing-garbage-collector-overhead-by-cutting-allocations-295l" class="crayons-story__hidden-navigation-link"&gt;High-Performance .NET: Reducing Garbage Collector Overhead by Cutting Allocations&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/alexey_popov" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4083826%2Fd51b5f95-52ad-4fed-9ca7-9342077b55e6.jpg" alt="alexey_popov profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/alexey_popov" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Alexey Popov
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Alexey Popov
                
                
              
              &lt;div id="story-author-preview-content-4669158" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/alexey_popov" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4083826%2Fd51b5f95-52ad-4fed-9ca7-9342077b55e6.jpg" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Alexey Popov&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/alexey_popov/high-performance-net-reducing-garbage-collector-overhead-by-cutting-allocations-295l" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 16&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/alexey_popov/high-performance-net-reducing-garbage-collector-overhead-by-cutting-allocations-295l" id="article-link-4669158"&gt;
          High-Performance .NET: Reducing Garbage Collector Overhead by Cutting Allocations
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dotnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dotnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/aspnetcore"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;aspnetcore&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/performance"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;performance&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/latencyspikes"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;latencyspikes&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/alexey_popov/high-performance-net-reducing-garbage-collector-overhead-by-cutting-allocations-295l#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>backend</category>
      <category>dotnet</category>
      <category>performance</category>
    </item>
    <item>
      <title>High-Performance .NET: Reducing Garbage Collector Overhead by Cutting Allocations</title>
      <dc:creator>Alexey Popov</dc:creator>
      <pubDate>Wed, 16 Sep 2026 14:38:47 +0000</pubDate>
      <link>https://dev.to/alexey_popov/high-performance-net-reducing-garbage-collector-overhead-by-cutting-allocations-295l</link>
      <guid>https://dev.to/alexey_popov/high-performance-net-reducing-garbage-collector-overhead-by-cutting-allocations-295l</guid>
      <description>&lt;p&gt;In the &lt;strong&gt;&lt;a href="https://dev.to/alexey_popov/understanding-memory-in-net-4pcc"&gt;Understanding Memory in .NET&lt;/a&gt;&lt;/strong&gt; article, we explored how memory management works in .NET and learned that objects allocated on the &lt;strong&gt;managed heap&lt;/strong&gt; eventually have to be processed by the &lt;strong&gt;garbage collector&lt;/strong&gt;. For most applications, garbage collection is barely noticeable. In high-throughput systems, however, millions of short-lived objects can trigger more frequent collections, increase latency, and reduce throughput [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals" rel="noopener noreferrer"&gt;1&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;Performance-sensitive .NET code therefore aims to avoid unnecessary allocations and reduce the amount of work left for the garbage collector.&lt;/p&gt;

&lt;p&gt;The techniques discussed in this article follow three general principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep small temporary buffers off the managed heap;&lt;/li&gt;
&lt;li&gt;avoid creating objects unnecessarily;&lt;/li&gt;
&lt;li&gt;reuse objects that have already been created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right approach depends on the problem: whether new storage is actually required, how long the data must remain valid, and whether an existing object can be reused.&lt;/p&gt;

&lt;p&gt;Let us see how these principles are applied in &lt;strong&gt;.NET 10&lt;/strong&gt; and &lt;strong&gt;ASP.NET Core 10&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep small temporary buffers off the managed heap
&lt;/h2&gt;

&lt;p&gt;A temporary buffer can be allocated as an ordinary array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An array is an object on the managed heap. Even if it is needed for only a single method call, the garbage collector will eventually have to process it. When a buffer is small and its size is known in advance, &lt;code&gt;stackalloc&lt;/code&gt; can allocate it on the stack instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;stackalloc&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This storage exists only until the method returns and does not create an object on the managed heap. Here, the result of &lt;code&gt;stackalloc&lt;/code&gt; is assigned to a &lt;code&gt;Span&amp;lt;byte&amp;gt;&lt;/code&gt;, so the buffer can be accessed through ordinary bounds-checked indexing without pointers or unsafe code [&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/stackalloc" rel="noopener noreferrer"&gt;2&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;A real-world example appears in the &lt;code&gt;TryDecodeMime&lt;/code&gt; method of &lt;code&gt;ContentDispositionHeaderValue&lt;/code&gt;, implemented in &lt;a href="https://github.com/dotnet/runtime/blob/v10.0.0/src/libraries/System.Net.Http/src/System/Net/Http/Headers/ContentDispositionHeaderValue.cs" rel="noopener noreferrer"&gt;&lt;code&gt;ContentDispositionHeaderValue.cs&lt;/code&gt;&lt;/a&gt;. A &lt;code&gt;Content-Disposition&lt;/code&gt; HTTP header may contain a MIME-encoded file name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;attachment; filename="=?utf-8?B?0L7RgtGH0ZHRgi50eHQ=?="
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To decode the file name, &lt;code&gt;TryDecodeMime&lt;/code&gt; splits the MIME encoded-word on the &lt;code&gt;?&lt;/code&gt; character. The supported format produces five parts, whose boundaries are stored in a small stack-allocated buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;stackalloc&lt;/span&gt; &lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;processedInputSpan&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processedInput&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// "=, encodingName, encodingType, encodedData, ="&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processedInputSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'?'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; 
    &lt;span class="n"&gt;processedInputSpan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="s"&gt;"\"="&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
    &lt;span class="n"&gt;processedInputSpan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="s"&gt;"=\""&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
    &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;processedInputSpan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ReadOnlySpan&amp;lt;char&amp;gt;.Split&lt;/code&gt; writes only the ranges of the resulting parts into &lt;code&gt;parts&lt;/code&gt;, but the characters themselves remain in the original string. Unlike &lt;code&gt;string.Split&lt;/code&gt;, this parsing step does not create an array and a separate string for every part.&lt;/p&gt;

&lt;p&gt;There are several important restrictions to keep in mind [&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/stackalloc" rel="noopener noreferrer"&gt;2&lt;/a&gt;]:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use &lt;code&gt;stackalloc&lt;/code&gt; only for small buffers with a controlled size, because the stack is much smaller than the heap, and an allocation that is too large can cause a &lt;code&gt;StackOverflowException&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;avoid &lt;code&gt;stackalloc&lt;/code&gt; inside loops because the allocated storage is released only when the method returns;&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; that refers to a local stack buffer cannot be returned from the method because the underlying storage will no longer exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the buffer size depends on input or can become large, use a bounded threshold and obtain the remaining storage another way. We will return to this topic a little later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid creating objects unnecessarily
&lt;/h2&gt;

&lt;p&gt;The most effective way to reduce allocations is to avoid creating a new object when the same work can be performed through a view over existing data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; to represent existing data
&lt;/h3&gt;

&lt;p&gt;In the previous example, &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; provided safe access to memory allocated with &lt;code&gt;stackalloc&lt;/code&gt;. The type is not limited to stack memory, however. &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; represents a contiguous region of memory backed by a stack buffer, an array, or unmanaged memory. The span itself neither owns nor copies that data [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/" rel="noopener noreferrer"&gt;3&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;Consider a simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// 99&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;slice&lt;/code&gt; represents two elements of the original array, starting at index &lt;code&gt;1&lt;/code&gt;. No new array is created, so a change made through the span is visible in &lt;code&gt;numbers&lt;/code&gt; as well.&lt;/p&gt;

&lt;p&gt;Now consider the &lt;code&gt;GetExtension&lt;/code&gt; method in &lt;a href="https://github.com/dotnet/runtime/blob/v10.0.0/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs" rel="noopener noreferrer"&gt;&lt;code&gt;Path.cs&lt;/code&gt;&lt;/a&gt;. It has two overloads: one accepts a string, while the other accepts a &lt;code&gt;ReadOnlySpan&amp;lt;char&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;GetExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The string overload is only a wrapper around the core implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;GetExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;GetExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsSpan&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual search for the extension is implemented by the &lt;code&gt;ReadOnlySpan&amp;lt;char&amp;gt;&lt;/code&gt; overload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;--)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PathInternal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDirectorySeparator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation returns a range within the original path rather than a new string. Calling &lt;code&gt;path.AsSpan()&lt;/code&gt; in the string overload does not allocate either. A new object is created only by &lt;code&gt;ToString()&lt;/code&gt;, when the result has to become an independent string.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;Memory&amp;lt;T&amp;gt;&lt;/code&gt; when a view must be retained
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; is declared as a &lt;code&gt;ref struct&lt;/code&gt;. This allows it to safely represent stack memory, but it also requires the compiler to restrict its lifetime. Those restrictions apply even when a particular span is backed by an ordinary managed array: it cannot be stored in a field of a regular object or kept across an &lt;code&gt;await&lt;/code&gt; boundary [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/" rel="noopener noreferrer"&gt;3&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;Some operations need to retain a view of memory and access it later. &lt;code&gt;Memory&amp;lt;T&amp;gt;&lt;/code&gt; is designed for this purpose. The distinction is easy to see in the signatures of two overloads from &lt;a href="https://github.com/dotnet/runtime/blob/v10.0.0/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs" rel="noopener noreferrer"&gt;&lt;code&gt;Stream.cs&lt;/code&gt;&lt;/a&gt; [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/" rel="noopener noreferrer"&gt;3&lt;/a&gt;]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The synchronous &lt;code&gt;Read&lt;/code&gt; completes before control returns to its caller, so it can accept a &lt;code&gt;Span&amp;lt;byte&amp;gt;&lt;/code&gt;. &lt;code&gt;ReadAsync&lt;/code&gt;, by contrast, may return an incomplete &lt;code&gt;ValueTask&amp;lt;int&amp;gt;&lt;/code&gt; and continue writing later. It therefore needs a representation of the buffer that can be retained, which is why it accepts &lt;code&gt;Memory&amp;lt;byte&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Derived stream types can override these methods and operate directly on the supplied memory. We will examine the base implementation of &lt;code&gt;ReadAsync(Memory&amp;lt;byte&amp;gt;)&lt;/code&gt; later.&lt;/p&gt;

&lt;p&gt;An array can be passed to an asynchronous method as &lt;code&gt;Memory&amp;lt;byte&amp;gt;&lt;/code&gt; without copying its contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsMemory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;bytesRead&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;[..&lt;/span&gt;&lt;span class="n"&gt;bytesRead&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AsMemory()&lt;/code&gt; does not create another buffer. The &lt;code&gt;Memory&amp;lt;T&amp;gt;&lt;/code&gt; remains valid until the asynchronous operation completes, and after the &lt;code&gt;await&lt;/code&gt;, its &lt;code&gt;Span&lt;/code&gt; property provides access to the filled portion of the original array.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Memory&amp;lt;T&amp;gt;&lt;/code&gt; does not own the buffer. Until the asynchronous operation has completed, the caller must not reuse that memory or return it to a pool [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/memory-t-usage-guidelines" rel="noopener noreferrer"&gt;4&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;Both types therefore represent existing memory, but allow different lifetimes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; for immediate, synchronous access to data;&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;Memory&amp;lt;T&amp;gt;&lt;/code&gt; when the view must be retained, for example until an asynchronous operation completes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reuse objects that have already been created
&lt;/h2&gt;

&lt;p&gt;A view over existing data is not always enough. An operation may require a separate buffer or object. If that resource is needed repeatedly, it can be rented from a pool and returned after use instead of being created for every call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;MemoryPool&amp;lt;T&amp;gt;&lt;/code&gt; to reuse contiguous memory regions
&lt;/h3&gt;

&lt;p&gt;When an operation simply needs a contiguous region of memory, it can rent one from &lt;code&gt;MemoryPool&amp;lt;T&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IMemoryOwner&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MemoryPool&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Rent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minimumSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;[..&lt;/span&gt;&lt;span class="n"&gt;minimumSize&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadExactlyAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pool returns an &lt;code&gt;IMemoryOwner&amp;lt;T&amp;gt;&lt;/code&gt;, which explicitly owns the rented memory. Its &lt;code&gt;Memory&amp;lt;T&amp;gt;&lt;/code&gt; can be stored and passed to asynchronous operations while the owner remains alive. Calling &lt;code&gt;Dispose()&lt;/code&gt; releases the rented memory back to the pool [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/" rel="noopener noreferrer"&gt;3&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.buffers.memorypool-1?view=net-10.0" rel="noopener noreferrer"&gt;5&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;This abstraction is useful when an algorithm does not care what kind of storage backs the returned memory. A custom pool implementation can abstract over different kinds of backing storage.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;System.IO.Pipelines&lt;/code&gt; library uses &lt;code&gt;MemoryPool&amp;lt;T&amp;gt;&lt;/code&gt; to manage its internal buffers. A &lt;code&gt;Pipe&lt;/code&gt; connects a producer of data to a consumer. The producer might read bytes from a network connection or stream and write them through a &lt;code&gt;PipeWriter&lt;/code&gt;. The consumer retrieves those bytes through a &lt;code&gt;PipeReader&lt;/code&gt; and parses them. The pipeline manages the buffers between them, so application code does not have to allocate a new array for every operation [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/io/pipelines" rel="noopener noreferrer"&gt;6&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;The producer asks the &lt;code&gt;PipeWriter&lt;/code&gt; for memory and writes directly into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minimumSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;bytesRead&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Advance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytesRead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;FlushResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FlushAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The consumer receives the written data as a &lt;code&gt;ReadOnlySequence&amp;lt;byte&amp;gt;&lt;/code&gt;, which may span multiple memory regions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ReadResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ReadOnlySequence&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Parse the available data and determine the consumed and examined positions.&lt;/span&gt;

&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AdvanceTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consumed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;examined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;consumed&lt;/code&gt; position tells the pipeline which memory can be reused, while &lt;code&gt;examined&lt;/code&gt; indicates how far the consumer inspected the data. After calling &lt;code&gt;AdvanceTo&lt;/code&gt;, code must not retain references to the released portion of the buffer [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/io/pipelines" rel="noopener noreferrer"&gt;6&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FlushAsync&lt;/code&gt; helps coordinate the rate of production with the rate of consumption. If too much unprocessed data accumulates, the producer pauses until the consumer releases part of the buffer. This helps contain buffer growth when the consumer processes data more slowly than the producer generates it [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/io/pipelines" rel="noopener noreferrer"&gt;6&lt;/a&gt;].&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;ArrayPool&amp;lt;T&amp;gt;&lt;/code&gt; to reuse arrays
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;MemoryPool&amp;lt;T&amp;gt;&lt;/code&gt; is not suitable for every situation. Some APIs specifically require a &lt;code&gt;T[]&lt;/code&gt;, so an ordinary array is needed to call them. &lt;code&gt;ArrayPool&amp;lt;T&amp;gt;&lt;/code&gt; can provide one without allocating a new array for every operation [&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1?view=net-10.0" rel="noopener noreferrer"&gt;7&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;The base implementation of &lt;code&gt;Stream.ReadAsync(Memory&amp;lt;byte&amp;gt;)&lt;/code&gt;, which we mentioned earlier, demonstrates this approach. If the memory is not backed by an accessible array, the fallback path rents a temporary &lt;code&gt;byte[]&lt;/code&gt; from &lt;code&gt;ArrayPool&amp;lt;byte&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;sharedBuffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArrayPool&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Rent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;FinishReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sharedBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sharedBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;FinishReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;readTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;localBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;localDestination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;readTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;localBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;CopyTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;localDestination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ArrayPool&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;localBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;Rent()&lt;/code&gt; replaces a temporary &lt;code&gt;new byte[...]&lt;/code&gt; allocation, while the &lt;code&gt;finally&lt;/code&gt; block ensures that &lt;code&gt;Return()&lt;/code&gt; gives the array back to the pool even if the read fails.&lt;/p&gt;

&lt;p&gt;Unlike &lt;code&gt;MemoryPool&amp;lt;T&amp;gt;&lt;/code&gt;, this API does not return a separate owner object. &lt;code&gt;Rent()&lt;/code&gt; returns the array itself, and its length may be greater than requested. The caller must track the amount of valid data and return the same array manually with &lt;code&gt;Return()&lt;/code&gt; [&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1?view=net-10.0" rel="noopener noreferrer"&gt;7&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;The array must not be accessed after it has been returned because another part of the program may rent it next. The pool also does not clear arrays automatically. For sensitive data, pass &lt;code&gt;clearArray: true&lt;/code&gt; to &lt;code&gt;Return()&lt;/code&gt; or clear the region that was used before returning the array [&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1?view=net-10.0" rel="noopener noreferrer"&gt;7&lt;/a&gt;].&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;ObjectPool&amp;lt;T&amp;gt;&lt;/code&gt; to reuse ready-to-use objects
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;MemoryPool&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;ArrayPool&amp;lt;T&amp;gt;&lt;/code&gt; reuse regions of memory. Sometimes, however, the resource to reuse is an object itself, together with its configuration and internal state. &lt;code&gt;ObjectPool&amp;lt;T&amp;gt;&lt;/code&gt; is intended for this scenario [&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/objectpool?view=aspnetcore-10.0" rel="noopener noreferrer"&gt;8&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;A real example appears in &lt;a href="https://github.com/dotnet/aspnetcore/blob/v10.0.0/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs" rel="noopener noreferrer"&gt;&lt;code&gt;NewtonsoftJsonInputFormatter.cs&lt;/code&gt;&lt;/a&gt; in ASP.NET Core 10. This component deserializes HTTP request bodies using Newtonsoft.Json's &lt;code&gt;JsonSerializer&lt;/code&gt;. Instead of creating and configuring a new serializer for every request, the formatter keeps a pool of ready-to-use instances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;ObjectPool&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;_jsonSerializerPool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt; &lt;span class="nf"&gt;CreateJsonSerializer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_jsonSerializerPool&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_jsonSerializerPool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_objectPoolProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JsonSerializerObjectPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SerializerSettings&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_jsonSerializerPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before deserialization, the formatter obtains a serializer from the pool and attaches an error handler for the current request. In the &lt;code&gt;finally&lt;/code&gt; block, it removes that handler and returns the object to the pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;jsonSerializer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateJsonSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;jsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;ErrorHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;finally&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;jsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;ErrorHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;ReleaseJsonSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonSerializer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ReleaseJsonSerializer&lt;/code&gt; returns the used serializer to the pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ReleaseJsonSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonSerializer&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_jsonSerializerPool&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="nf"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Removing the handler before returning the serializer is essential: the next request may receive the same instance and must not inherit state from the previous operation. Unlike the memory pools, this pool reuses a configured &lt;code&gt;JsonSerializer&lt;/code&gt; object with its own behavior and state, not merely an array of elements.&lt;/p&gt;

&lt;p&gt;Pooling is worthwhile only for objects that are expensive to create or initialize and are used frequently enough. &lt;code&gt;ObjectPool&amp;lt;T&amp;gt;&lt;/code&gt; limits how many objects it retains, not how many it can create. If no instance is available, &lt;code&gt;Get()&lt;/code&gt; creates a new one. Once an object has been returned, it must no longer be used because another caller may acquire it [&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/objectpool?view=aspnetcore-10.0" rel="noopener noreferrer"&gt;8&lt;/a&gt;].&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Despite their differences, all the techniques discussed here follow three general principles: keep small temporary buffers off the managed heap, avoid creating objects unnecessarily, and reuse objects that have already been created.&lt;/p&gt;

&lt;p&gt;Choosing the right tool depends on the lifetime of the data and how it will be used. A small local buffer can live on the stack, existing data can be exposed through a non-copying view, and a resource that must be created can be returned to a pool for reuse.&lt;/p&gt;

&lt;p&gt;The goal is not to eliminate every allocation. The final string, object, or array is often genuinely necessary. The important distinction is between data the application actually needs and temporary objects introduced only by the way that data is processed.&lt;/p&gt;

&lt;p&gt;Begin this kind of optimization with measurements. More complex memory management is justified only when reducing garbage-collection work produces a meaningful benefit and the ownership rules remain clear.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals" rel="noopener noreferrer"&gt;Microsoft Learn - Fundamentals of garbage collection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/stackalloc" rel="noopener noreferrer"&gt;Microsoft Learn - &lt;code&gt;stackalloc&lt;/code&gt; expression&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/" rel="noopener noreferrer"&gt;Microsoft Learn - Memory- and span-related types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/memory-t-usage-guidelines" rel="noopener noreferrer"&gt;Microsoft Learn - &lt;code&gt;Memory&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; usage guidelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.buffers.memorypool-1?view=net-10.0" rel="noopener noreferrer"&gt;Microsoft Learn - &lt;code&gt;MemoryPool&amp;lt;T&amp;gt;&lt;/code&gt; Class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/io/pipelines" rel="noopener noreferrer"&gt;Microsoft Learn - &lt;code&gt;System.IO.Pipelines&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1?view=net-10.0" rel="noopener noreferrer"&gt;Microsoft Learn - &lt;code&gt;ArrayPool&amp;lt;T&amp;gt;&lt;/code&gt; Class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/objectpool?view=aspnetcore-10.0" rel="noopener noreferrer"&gt;Microsoft Learn - Object reuse with &lt;code&gt;ObjectPool&amp;lt;T&amp;gt;&lt;/code&gt; in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>dotnet</category>
      <category>aspnetcore</category>
      <category>performance</category>
      <category>latencyspikes</category>
    </item>
    <item>
      <title>Understanding Memory in .NET</title>
      <dc:creator>Alexey Popov</dc:creator>
      <pubDate>Thu, 03 Sep 2026 15:08:16 +0000</pubDate>
      <link>https://dev.to/alexey_popov/understanding-memory-in-net-4pcc</link>
      <guid>https://dev.to/alexey_popov/understanding-memory-in-net-4pcc</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;With the rise of large language models (LLMs), the distinction between &lt;em&gt;writing code&lt;/em&gt; and &lt;em&gt;programming&lt;/em&gt; has become more apparent than ever.&lt;/p&gt;

&lt;p&gt;To an LLM, source code is merely text. To a computer, it is a sequence of instructions. A developer’s task is to understand what actually happens when those instructions are executed.&lt;/p&gt;

&lt;p&gt;This is why studying a programming language often begins not with syntax, but with the program’s execution model. One of the most important parts of that model is memory: how data is represented, how it is copied, how long it remains alive, and how different pieces of data interact while a program is running.&lt;/p&gt;

&lt;p&gt;In this article, we will explore two primary memory regions used by .NET applications, why they serve different purposes, and how this design influences both the behavior and performance of .NET applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory Regions
&lt;/h2&gt;

&lt;p&gt;When a .NET application starts, the operating system creates a process and its first execution thread (&lt;em&gt;primary thread&lt;/em&gt;). The &lt;strong&gt;Common Language Runtime (CLR)&lt;/strong&gt; is then initialized and takes responsibility for executing the application, including managing the &lt;strong&gt;Managed Heap&lt;/strong&gt; and the &lt;strong&gt;Garbage Collector&lt;/strong&gt; [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/threading/threads-and-threading" rel="noopener noreferrer"&gt;1&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;During execution, additional threads may be created by the application or used by the runtime. Each thread has its own &lt;strong&gt;Stack&lt;/strong&gt; and CPU register state [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/threading/threads-and-threading" rel="noopener noreferrer"&gt;1&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;As a result, a running .NET application relies on two primary memory regions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stack&lt;/strong&gt; - private to each thread and used for method execution and local state [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/threading/threads-and-threading" rel="noopener noreferrer"&gt;1&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed Heap&lt;/strong&gt; - shared by threads in the process and managed by the CLR's Garbage Collector [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals" rel="noopener noreferrer"&gt;2&lt;/a&gt;]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But why does the platform need two different memory regions?&lt;/p&gt;

&lt;p&gt;Because different kinds of data have different requirements regarding access speed, lifetime, and storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Representation
&lt;/h2&gt;

&lt;p&gt;In .NET, there are two fundamental ways of representing data: &lt;strong&gt;Value Types&lt;/strong&gt; and &lt;strong&gt;Reference Types&lt;/strong&gt; [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/base-types/common-type-system" rel="noopener noreferrer"&gt;3&lt;/a&gt;].&lt;/p&gt;

&lt;h3&gt;
  
  
  Value Types
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Value Types&lt;/strong&gt; are used when only the value itself matters.&lt;/p&gt;

&lt;p&gt;Consider a simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the assignment, the program contains two independent copies of the value. Changing one variable has no effect on the other because each variable stores its own value [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/base-types/common-type-system" rel="noopener noreferrer"&gt;3&lt;/a&gt;]:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnvnuthprtfbchqjary8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnvnuthprtfbchqjary8v.png" alt="Figure 1" width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. Assigning a Value Type creates an independent copy of the value&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From the CLR’s perspective, such data does not need a distinct identity because only the value matters. Its exact storage location is therefore determined entirely by the context in which it is used.&lt;/p&gt;

&lt;p&gt;This is why small, independent pieces of data (numbers, coordinates, dates, dimensions, or colors) are commonly represented as &lt;strong&gt;Value Types&lt;/strong&gt; in .NET.&lt;/p&gt;

&lt;p&gt;For example, an application may store the coordinates of millions of points on a map or the vertices of a three-dimensional model. Each point is merely a set of numbers. Copying such values is a natural process and does not require the creation of separate objects with their own identities.&lt;/p&gt;
&lt;h3&gt;
  
  
  Reference Types
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Reference Types&lt;/strong&gt;, by contrast, are used when not only the information matters, but also the identity of the entity to which that information belongs.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;User&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;user2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the &lt;code&gt;user1&lt;/code&gt; and &lt;code&gt;user2&lt;/code&gt; assignments, both variables refer to the same object [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/base-types/common-type-system" rel="noopener noreferrer"&gt;3&lt;/a&gt;]:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftcwt4d8b0imyy1ko2813.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftcwt4d8b0imyy1ko2813.png" alt="Figure 2" width="751" height="797"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. Assigning a Reference Type copies the reference, not the object&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Such an object may be passed between methods, returned from them, stored in collections, and remain alive far longer than any individual method call. Its lifetime must therefore not depend on a particular stack frame. Instances of &lt;strong&gt;Reference Types&lt;/strong&gt; are allocated on the &lt;strong&gt;Managed Heap&lt;/strong&gt; [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals" rel="noopener noreferrer"&gt;2&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reference Types&lt;/strong&gt; are therefore well suited to modeling entities shared by multiple parts of a program. An object of &lt;code&gt;User&lt;/code&gt; class, for example, may be displayed in the user interface, stored in a collection, passed to a service, and used by an authentication system. All of these components must work with the same object instance rather than with independent copies.&lt;/p&gt;

&lt;p&gt;The same principle underlies most dynamic data structures. A linked list, for instance, consists of nodes, each of which stores a reference to the next node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reference semantics make this representation natural: nodes can be connected and rearranged by changing references without copying the nodes themselves.&lt;/p&gt;

&lt;p&gt;Choosing between &lt;strong&gt;Value Types&lt;/strong&gt; and &lt;strong&gt;Reference Types&lt;/strong&gt; is therefore not primarily a matter of performance or coding style - it is a matter of modeling data correctly. As a general modeling principle, &lt;strong&gt;Value Types&lt;/strong&gt; are well suited to independent values, while &lt;strong&gt;Reference Types&lt;/strong&gt; are well suited to entities whose identity and shared state matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boxing and Unboxing
&lt;/h2&gt;

&lt;p&gt;.NET is built around an unified type system whose root is &lt;code&gt;System.Object&lt;/code&gt; [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/base-types/common-type-system" rel="noopener noreferrer"&gt;3&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;As a result, a value of any type can be treated as an object when necessary [&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing" rel="noopener noreferrer"&gt;4&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Today&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;string&lt;/code&gt; is already a &lt;strong&gt;Reference Type&lt;/strong&gt;, but &lt;code&gt;42&lt;/code&gt; and &lt;code&gt;DateTime.Today&lt;/code&gt; are &lt;strong&gt;Value Types&lt;/strong&gt;. So how can they all be stored in the same collection?&lt;/p&gt;

&lt;h3&gt;
  
  
  Boxing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Boxing&lt;/strong&gt; is the implicit conversion of a &lt;strong&gt;Value Type&lt;/strong&gt; to &lt;code&gt;object&lt;/code&gt; or to an interface implemented by that type [&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing" rel="noopener noreferrer"&gt;4&lt;/a&gt;]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;456&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During boxing, the CLR [&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing" rel="noopener noreferrer"&gt;4&lt;/a&gt;]:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Allocates memory on the &lt;strong&gt;Managed Heap&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Creates a new object.&lt;/li&gt;
&lt;li&gt;Copies the value into that object.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsc4e4vcqa5grqigyua8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsc4e4vcqa5grqigyua8.png" alt="Figure 3" width="799" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. Boxing creates a new object on the Managed Heap containing a copy of the value&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Boxing &lt;strong&gt;does not move&lt;/strong&gt; a value from the &lt;strong&gt;Stack&lt;/strong&gt; to the &lt;strong&gt;Managed Heap&lt;/strong&gt;. Instead, the CLR allocates a new object on the heap and copies the value into it [&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing" rel="noopener noreferrer"&gt;4&lt;/a&gt;]. The original value remains independent of the boxed copy.&lt;/p&gt;

&lt;p&gt;Every boxing operation therefore creates an additional heap object. Although allocating memory on the Managed Heap is generally fast, repeated boxing creates additional allocations and can increase the amount of work the &lt;strong&gt;Garbage Collector&lt;/strong&gt; must eventually perform [&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing" rel="noopener noreferrer"&gt;4&lt;/a&gt;].&lt;/p&gt;

&lt;h3&gt;
  
  
  Unboxing
&lt;/h3&gt;

&lt;p&gt;After boxing, the value is represented as an &lt;code&gt;object&lt;/code&gt;. To use it again as an &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;DateTime&lt;/code&gt;, or any other &lt;strong&gt;Value Type&lt;/strong&gt;, it must first be &lt;strong&gt;unboxed&lt;/strong&gt; [&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing" rel="noopener noreferrer"&gt;4&lt;/a&gt;]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During unboxing, the CLR [&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing" rel="noopener noreferrer"&gt;4&lt;/a&gt;]:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verifies that the object contains a value of the expected type.&lt;/li&gt;
&lt;li&gt;Copies that value into a new variable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fscu2rzvzz860xep0blrb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fscu2rzvzz860xep0blrb.png" alt="Figure 4" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4. Unboxing copies the value from the boxed object into a new Value Type variable&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The boxed object itself remains on the &lt;strong&gt;Managed Heap&lt;/strong&gt; until it eventually becomes unreachable, and its memory can be reclaimed by the &lt;strong&gt;Garbage Collector&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Garbage Collector
&lt;/h2&gt;

&lt;p&gt;As long as an object exists on the &lt;strong&gt;Managed Heap&lt;/strong&gt;, the CLR must eventually determine when its memory can be reclaimed. This responsibility belongs to the &lt;strong&gt;Garbage Collector&lt;/strong&gt;, which serves as .NET's automatic memory manager [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals" rel="noopener noreferrer"&gt;2&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;When a garbage collection is triggered, the &lt;strong&gt;Garbage Collector&lt;/strong&gt; examines the object graph starting from a set of &lt;strong&gt;Garbage Collector Roots&lt;/strong&gt;, determining which objects are still reachable. Objects that cannot be reached from these roots are considered garbage and their memory can be reclaimed [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals" rel="noopener noreferrer"&gt;2&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;Allocating objects on the managed heap is generally very fast because, while space is available, allocation primarily involves advancing a pointer [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals" rel="noopener noreferrer"&gt;2&lt;/a&gt;]. However, increasing the number and rate of heap allocations increases the amount of work the &lt;strong&gt;Garbage Collector&lt;/strong&gt; may need to perform.&lt;/p&gt;

&lt;p&gt;Moreover, if an object remains reachable - for example, through a static field or a collection - the GC considers it alive and will not reclaim its memory. Keeping references to objects that are no longer logically needed can therefore cause memory leaks even in managed applications [&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals" rel="noopener noreferrer"&gt;2&lt;/a&gt;].&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Looking at the complete picture, it becomes clear that these mechanisms are not isolated features but parts of a single execution model.&lt;/p&gt;

&lt;p&gt;Understanding this model allows us to see .NET not as a collection of unrelated features, but as a coherent system whose components naturally build upon one another. This is why understanding the memory model forms the foundation for understanding many other parts of the modern .NET platform, including &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;stackalloc&lt;/code&gt;, &lt;code&gt;async/await&lt;/code&gt;, &lt;code&gt;yield&lt;/code&gt;, and many others.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/threading/threads-and-threading" rel="noopener noreferrer"&gt;Microsoft Learn - Threads and threading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals" rel="noopener noreferrer"&gt;Microsoft Learn - Fundamentals of garbage collection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/base-types/common-type-system" rel="noopener noreferrer"&gt;Microsoft Learn - Common type system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing" rel="noopener noreferrer"&gt;Microsoft Learn - Boxing and Unboxing&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>dotnet</category>
      <category>beginners</category>
      <category>csharp</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
