PinnedMemory 2.0.1
dotnet add package PinnedMemory --version 2.0.1
NuGet\Install-Package PinnedMemory -Version 2.0.1
<PackageReference Include="PinnedMemory" Version="2.0.1" />
<PackageVersion Include="PinnedMemory" Version="2.0.1" />
<PackageReference Include="PinnedMemory" />
paket add PinnedMemory --version 2.0.1
#r "nuget: PinnedMemory, 2.0.1"
#:package PinnedMemory@2.0.1
#addin nuget:?package=PinnedMemory&version=2.0.1
#tool nuget:?package=PinnedMemory&version=2.0.1
PinnedMemory
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?
- Install
- Supported platforms and types
- Quick start
- API behavior at a glance
- Usage patterns
- Security and correctness best practices
- Performance notes
- Common pitfalls
- FAQ
- License
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
sbytebytecharshortushortintuintlongulongfloatdoubledecimalbool
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: trueandlocked: 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); whenUnknown, 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 newPinnedMemory<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
Always dispose promptly
- Use
using/using var. - Do not rely on process shutdown for cleanup.
- Use
Choose
zerointentionallyzero: truefor allocate-then-populate flows.zero: falsefor pre-populated input arrays.
Avoid converting secrets to
stringstringis immutable and not controllably zeroable.- Keep secrets in pinned
byte[]/char[]as long as possible.
Minimize copies
Read()/ToArray()expose the internal buffer reference.- If you copy data elsewhere, you now have additional memory to scrub.
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).
Do not access after dispose
- The underlying buffer is returned to
ArrayPool<T>. - Retained references can observe reused data from other code.
- The underlying buffer is returned to
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>.Sharedto 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: falsewhen preserving initial values.
- You likely used default
“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 throwsSecurityException.
- No.
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 | Versions 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. |
-
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.