SpanString 0.2.1

dotnet add package SpanString --version 0.2.1
NuGet\Install-Package SpanString -Version 0.2.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="SpanString" Version="0.2.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add SpanString --version 0.2.1
#r "nuget: SpanString, 0.2.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.
// Install SpanString as a Cake Addin
#addin nuget:?package=SpanString&version=0.2.1

// Install SpanString as a Cake Tool
#tool nuget:?package=SpanString&version=0.2.1

SpanString

SpanString is an optimized string data structure built from Memory<char> and Span<char> added in .NET Standard 2.1+ and .NET Core 3.0+. It allows you to gather string segments from the memory of other strings to act as a single string. This can be useful for applications where millions of strings are parsed and used as keys for dictionaries or other data structures, and memory pressure and GC overhead are important concerns.

SpanString always uses ordinal string comparisons, and currently only handles ANSI for OrdinalIgnoreCase comparisons.

The release output of this repo generates the SpanString package on Nuget.org.

Usage

SpanStrings are exposed in the OptimizedStrings namespace.

SpanString

The SpanString class acts as a factory for various construction patterns, returning ISpanString instances. These instances are boxed versions of the SpanString1, 2, ... classes noted below.

SpanStringComparer

Provides various static helpers like Ordinal and OrdinalIgnoreCase for use as the comparers for SpanStrings used in hashmap-type collections.

SpanString1 struct

A SpanString1 wraps all or part of an underlying string object with a ReadOnlyMemory<char> to allow slicing out a subset of a string. The SpanString1 keeps a ref to the underlying string while letting you use it for a subset of string operations. It is a struct to let you control memory placement and avoid heap allocation overhead; if you want a boxed version allocated on the heap, use SpanString.Create to get an ISpanString that boxes this struct.

Use cases: One typical case for this is when parsing paths and you want to use one long path as the basis for multiple subpaths, all kept as references to parts of the long path string. SpanString1 implements IEquatable and can act as its own default ordinally-compared key in a Dictionary, ConcurrentDictionary, HashSet, or other hashmap-like collection, or use SpanStringComparer.OrdinalIgnoreCase as the comparer to get case-insensitive keys.

using OptimizedStrings;

var windowsRelativePaths = new Dictionary<SpanString1, DirectoryInfo>(SpanStringComparer.OrdinalIgnoreCase);

string aPath = @"c:\windows\Microsoft.NET\Framework64\v4.0.30319\System.Web.Services.dll";

// No need to do aPath.Substring(11) to create a
// new string after 'c:\windows\' - just slice it!
var ss = new SpanString1(aPath, 11);
windowsRelativePaths[ss] = new DirectoryInfo(aPath);

SpanString2 struct

A SpanString2 wraps two underlying strings and treats them as one for comparison, equality, hash code, and other calculations. It keeps two refs to the underlying strings' memory. The two strings can actually be from the same string. It is a struct to let you control memory placement and avoid heap allocation overhead; if you want a boxed version allocated on the heap, use SpanString.Create to get an ISpanString that boxes this struct.

Use cases: Creating a multi-part string key from two individual parts becomes more efficient when you don't need to allocate a third string that concatenates the two parts together. Instead use a SpanString2 to wrap both parts and use as a key in a Dictionary, ConcurrentDictionary, HashSet, and so on. SpanString2 implements IEquatable and can act as its own default ordinally-compared key in a Dictionary, HashSet, or other hashmap-like collection. Or use SpanStringComparer.OrdinalIgnoreCase as the comparer to get case-insensitive keys.

using OptimizedStrings;

class MyDatabaseRecord
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address1 { get; set; }
    public string Address2 { get; set; }

    public SpanString2 ToKey()
    {
        return new SpanString2(FirstName, LastName);
    }
}

var table = new Dictionary<SpanString2, DirectoryInfo>(SpanStringComparer.OrdinalIgnoreCase);

foreach (MyDatabaseRecord record in database.GetRecords())
{
    table[record.ToKey()] = record;
}

Release Notes

  • 0.2.1 Mar 2023: Add nullable annotations.
  • 0.2.0 Mar 2023: Retarget to .NET 6 only.
  • 0.1.0 Apr 2019: Initial release for testing performance and memory use versus strings in cases of reading and parsing millions of strings and creating substrings. Only contains SpanString1,2 allowing up to only 2 string segments to compose a SpanString.
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.2.1 211 3/16/2023
0.2.0 176 3/16/2023
0.1.0 669 4/23/2019