PinnedMemory 2.0.1

dotnet add package PinnedMemory --version 2.0.1
                    
NuGet\Install-Package PinnedMemory -Version 2.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PinnedMemory" Version="2.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PinnedMemory" Version="2.0.1" />
                    
Directory.Packages.props
<PackageReference Include="PinnedMemory" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PinnedMemory --version 2.0.1
                    
#r "nuget: PinnedMemory, 2.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PinnedMemory@2.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PinnedMemory&version=2.0.1
                    
Install as a Cake Addin
#tool nuget:?package=PinnedMemory&version=2.0.1
                    
Install as a Cake Tool

PinnedMemory

License: MIT nuget

PinnedMemory is a lightweight .NET library for handling sensitive or performance-critical arrays that should stay pinned in memory, be zeroed, and optionally be memory-locked at the OS level.

It is designed for scenarios such as:

  • handling secrets in byte/char buffers,
  • passing stable pointers to native interop,
  • reducing GC relocation concerns for low-level memory operations.

Table of Contents


Why use PinnedMemory?

When managed arrays are not pinned, the GC may move them. PinnedMemory<T> pins an array for the lifetime of the object and can also:

  • zero the buffer,
  • lock memory pages (platform permitting),
  • zero + unlock on dispose.

This gives you a safer lifecycle for sensitive data and a predictable address for native interop work.


Target framework

This project targets .NET 8 (net8.0) by default.

Install

.NET CLI

dotnet add package PinnedMemory

Package Manager Console

Install-Package PinnedMemory

NuGet


Supported platforms and types

Platforms

  • Windows
  • Linux
  • macOS
  • Android
  • iOS

Supported T element types

  • sbyte
  • byte
  • char
  • short
  • ushort
  • int
  • uint
  • long
  • ulong
  • float
  • double
  • decimal
  • bool

If you use an unsupported struct, construction throws ArrayTypeMismatchException.


Quick start

using PinnedMemory;

var bytes = new byte[32];

using var secret = new PinnedMemory<byte>(bytes);

secret[0] = 0x41;
secret.Write(1, 0x42);

var first = secret.Read(0);
var copy = secret.ToArray();

By default, construction uses zero: true and locked: true.


API behavior at a glance

Constructor

PinnedMemory(T[] value, bool zero = true, bool locked = true, SystemType type = SystemType.Unknown)
  • value: source array copied into an internal pooled buffer.
  • zero:
    • true (default): internal memory is zeroed immediately after allocation.
    • false: initial contents are preserved.
  • locked:
    • true (default): attempts OS-level page lock.
    • false: skip lock attempt.
  • type: optional explicit OS selection (Windows, Linux, Osx, Android, Ios); when Unknown, OS is detected automatically at runtime.

Important lifecycle semantics

  • Construction rents an internal buffer from ArrayPool<T>.Shared.
  • Dispose():
    • zeroes internal memory,
    • unlocks if locking was requested,
    • frees the pin handle,
    • returns the buffer to the pool.

Members

  • Length: logical length from the original input array.
  • Indexer this[int i]: get/set values.
  • Read(): returns internal buffer reference.
  • Read(int index): read one value.
  • Write(int index, T value): write one value.
  • ToArray(): returns internal buffer reference.
  • Clone(): deep-copies into a new PinnedMemory<T>.

Usage patterns

1) Preserve initial bytes on creation

Use zero: false when you provide meaningful input data.

using var pin = new PinnedMemory<byte>(new byte[] { 0x10, 0x20, 0x30 }, zero: false);

2) Build value incrementally

Keep default zero: true and fill explicitly.

using var pin = new PinnedMemory<byte>(new byte[3]);
pin[0] = 65;
pin[1] = 61;
pin[2] = 77;

3) Clone for isolated lifecycle

using var original = new PinnedMemory<byte>(new byte[] { 1, 2, 3 }, zero: false);
using var clone = original.Clone();

4) char[] for sensitive text-like data

using var text = new PinnedMemory<char>(new[] { 's', 'e', 'c', 'r', 'e', 't' }, zero: false);

Prefer char[]/byte[] over string when handling secrets.


Security and correctness best practices

  1. Always dispose promptly

    • Use using / using var.
    • Do not rely on process shutdown for cleanup.
  2. Choose zero intentionally

    • zero: true for allocate-then-populate flows.
    • zero: false for pre-populated input arrays.
  3. Avoid converting secrets to string

    • string is immutable and not controllably zeroable.
    • Keep secrets in pinned byte[]/char[] as long as possible.
  4. Minimize copies

    • Read()/ToArray() expose the internal buffer reference.
    • If you copy data elsewhere, you now have additional memory to scrub.
  5. Treat locking as best effort

    • OS lock calls can be constrained by permissions/limits.
    • Keep least-privilege defaults in deployment (e.g., memlock limits on Linux).
  6. Do not access after dispose

    • The underlying buffer is returned to ArrayPool<T>.
    • Retained references can observe reused data from other code.
  7. Keep scope small

    • Hold pinned memory only for the shortest practical duration.
    • Long-lived pinned regions can negatively affect GC behavior.

Performance notes

  • The library uses ArrayPool<T>.Shared to reduce allocation pressure.
  • Pinning and OS page-locking have costs; use only where justified.
  • For small, frequent operations, benchmark your real workload with and without locking.

Common pitfalls

  • “My provided bytes are all zero.”

    • You likely used default zero: true.
    • Set zero: false when preserving initial values.
  • “I used ToArray() then disposed, but still read old reference.”

    • ToArray() returns internal buffer reference, not a defensive copy.
    • Do not keep references past disposal.
  • “Can I call ToString() to inspect data?”

    • No. ToString() intentionally throws SecurityException.

FAQ

Is this a replacement for cryptographic key vaulting/HSMs?

No. It helps memory hygiene inside your process. It is not a full secret-management system.

Is memory lock guaranteed?

No. Locking is platform- and permission-dependent.

Is PinnedMemory<T> thread-safe?

No explicit thread-safety guarantees are provided. Synchronize concurrent access at the caller boundary.


License

This project is licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages (10)

Showing the top 5 NuGet packages that depend on PinnedMemory:

Package Downloads
Blake2b.NetCore

Implementation of the cryptographic hash function BLAKE2b. Optimized for PinnedMemory, and 64-bit platform.

SecureRandom.NetCore

Implementation of a cryptographic pseudorandom number generator (CPRNG) using Blake2b. Optimized for PinnedMemory.

ChaCha20.NetCore

Implementation of ChaCha20 cipher (RFC 8439 style nonce), optimized for pinned memory usage.

Argon2.NetCore

Implementation of Argon2 key derivation function designed by Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich. Optimized for PinnedMemory.

AeadChaCha20Poly1305.NetCore

Implementation of AEAD_CHACHA20_POLY1305 authenticated encryption with additional data using ChaCha20 and Poly1305, optimized for PinnedMemory and .NET.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.1 48 2/23/2026
2.0.0 358 10/14/2024
1.0.1 17,456 7/11/2020
1.0.0 626 7/11/2020