Skip to main content

spreadcheetah by Sveinung

Nuget / site data

Nuget GitHub last commit GitHub Repo stars

Details

Info

info

Name: spreadcheetah

SpreadCheetah is a high-performance .NET library for generating spreadsheet (Microsoft Excel XLSX) files.

Author: Sveinung

NuGet: https://www.nuget.org/packages/spreadcheetah/

You can find more details at https://github.com/sveinungf/spreadcheetah

Source : https://github.com/sveinungf/spreadcheetah

Original Readme

note

SpreadCheetah

Nuget

SpreadCheetah is a high-performance .NET library for generating spreadsheet (Microsoft Excel XLSX) files.

Features

  • Performance (see benchmarks below)
  • Low memory allocation (see benchmarks below)
  • Async APIs
  • No dependency to Microsoft Excel
  • Targeting .NET Standard 2.0 (for .NET Framework 4.6.1 and later)
  • Free and open source!

SpreadCheetah is designed to create spreadsheet files in a forward-only manner. That means worksheets from left to right, rows from top to bottom, and row cells from left to right. This allows for creating spreadsheet files in a streaming manner, while also keeping a low memory footprint.

Most basic spreadsheet functionality is supported, such as cells with different data types, basic styling, and formulas. More advanced functionality is planned for future releases.

How to install

SpreadCheetah is available as a NuGet package. The NuGet package targets .NET Standard 2.0 as well as newer versions of .NET. The .NET Standard 2.0 version is just intended for backwards compatibility (.NET Framework and earlier versions of .NET Core). More optimizations are enabled when targeting newer versions of .NET.

Basic usage

using (var spreadsheet = await Spreadsheet.CreateNewAsync(stream))
{
// A spreadsheet must contain at least one worksheet.
await spreadsheet.StartWorksheetAsync("Sheet 1");

// Cells are inserted row by row.
var row = new List<Cell>();
row.Add(new Cell("Answer to the ultimate question:"));
row.Add(new Cell(42));

// Rows are inserted from top to bottom.
await spreadsheet.AddRowAsync(row);

// Remember to call Finish before disposing.
// This is important to properly finalize the XLSX file.
await spreadsheet.FinishAsync();
}

Other examples

Using the Source Generator

Source Generators is a newly released feature in the C# compiler. SpreadCheetah includes a source generator that makes it easier to create rows from objects. It is used in a similar way to the System.Text.Json source generator:

namespace MyNamespace;

// A plain old C# class which we want to add as a row in a worksheet.
// The source generator will pick the properties with public getters.
// The order of the properties will decide the order of the cells.
public class MyObject
{
public string Question { get; set; }
public int Answer { get; set; }
}

The source generator will be instructed to generate code by defining a partial class like this:

using SpreadCheetah.SourceGeneration;

namespace MyNamespace;

[WorksheetRow(typeof(MyObject))]
public partial class MyObjectRowContext : WorksheetRowContext
{
}

During build, the type will be analyzed and an implementation of the context class will be created. We can then create a row from an object by calling AddAsRowAsync with the object and the context type as parameters:

await using var spreadsheet = await Spreadsheet.CreateNewAsync(stream);
await spreadsheet.StartWorksheetAsync("Sheet 1");

var myObj = new MyObject { Question = "How many Rings of Power were there?", Answer = 20 };

await spreadsheet.AddAsRowAsync(myObj, MyObjectRowContext.Default.MyObject);

await spreadsheet.FinishAsync();

Here is a peek at part of the code that was generated for this example:

// <auto-generated />
private static async ValueTask AddAsRowInternalAsync(Spreadsheet spreadsheet, MyObject obj, CancellationToken token)
{
var cells = ArrayPool<DataCell>.Shared.Rent(2);
try
{
cells[0] = new DataCell(obj.Question);
cells[1] = new DataCell(obj.Answer);
await spreadsheet.AddRowAsync(cells.AsMemory(0, 2), token).ConfigureAwait(false);
}
finally
{
ArrayPool<DataCell>.Shared.Return(cells, true);
}
}

The source generator can generate rows from classes, records, and structs. It can be used in all supported .NET versions, including .NET Framework, however the C# version must be 8.0 or greater.

Benchmarks

The benchmark results here have been collected using Benchmark.NET with the following system configuration:

BenchmarkDotNet=v0.13.2, OS=Windows 10 (10.0.19043.2251/21H1/May2021Update)
Intel Core i5-8600K CPU 3.60GHz (Coffee Lake), 1 CPU, 6 logical and 6 physical cores
.NET SDK=7.0.100
[Host] : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
.NET 6.0 : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
.NET Framework 4.8 : .NET Framework 4.8 (4.8.4515.0), X64 RyuJIT VectorSize=256

InvocationCount=1 UnrollFactor=1

The code executed in the benchmark creates a worksheet of 20 000 rows and 10 columns filled with string values. The same use case has been implemented in other spreadsheet libraries for comparison. Some of these libraries have multiple ways of achieving the same result, but to make this a fair comparison the idea is to use the most efficient approach for each library. The code is available here.

.NET Framework 4.8

LibraryMeanErrorStdDevAllocated
SpreadCheetah68.67 ms0.283 ms0.251 ms152.23 KB
Open XML (SAX approach)438.22 ms1.161 ms1.086 ms43 317.24 KB
EPPlus v4609.98 ms6.626 ms5.874 ms286 142.58 KB
Open XML (DOM approach)1,098.52 ms9.419 ms8.811 ms161 123.16 KB
ClosedXML1,618.57 ms7.088 ms6.630 ms565 074.91 KB

.NET 6

LibraryMeanErrorStdDevAllocated
SpreadCheetah28.53 ms0.079 ms0.070 ms6.48 KB
Open XML (SAX approach)250.65 ms0.541 ms0.480 ms66 049.91 KB
EPPlus v4405.90 ms1.782 ms1.579 ms195 790.25 KB
Open XML (DOM approach)775.74 ms14.404 ms14.147 ms182 926.06 KB
ClosedXML1,262.92 ms19.825 ms18.544 ms524 913.50 KB

.NET 7

LibraryMeanErrorStdDevAllocated
SpreadCheetah25.14 ms0.148 ms0.138 ms6.48 KB
Open XML (SAX approach)239.72 ms0.231 ms0.216 ms66 046.48 KB
EPPlus v4406.69 ms1.852 ms1.642 ms195 792.41 KB
Open XML (DOM approach)831.68 ms10.446 ms9.771 ms182 926.04 KB
ClosedXML1,171.07 ms8.106 ms7.186 ms524 846.85 KB

About

note

generating Excel from objects

does not work with DateTime

How to use

Example ( source csproj, source files )

This is the CSharp Project that references spreadcheetah

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="SpreadCheetah" Version="1.10.0" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>

Generated Files

Those are taken from $(BaseIntermediateOutputPath)\GX

// <auto-generated />
#nullable enable
using SpreadCheetah;
using SpreadCheetah.SourceGeneration;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace spreadcheetahDemo
{
public partial class PersonRowContext
{
private static PersonRowContext? _default;
public static PersonRowContext Default => _default ??= new PersonRowContext();

public PersonRowContext()
{
}

private WorksheetRowTypeInfo<spreadcheetahDemo.Person>? _Person;
public WorksheetRowTypeInfo<spreadcheetahDemo.Person> Person => _Person ??= WorksheetRowMetadataServices.CreateObjectInfo<spreadcheetahDemo.Person>(AddAsRowAsync, AddRangeAsRowsAsync);

private static ValueTask AddAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, spreadcheetahDemo.Person? obj, CancellationToken token)
{
if (spreadsheet is null)
throw new ArgumentNullException(nameof(spreadsheet));
if (obj is null)
return spreadsheet.AddRowAsync(ReadOnlyMemory<DataCell>.Empty, token);
return AddAsRowInternalAsync(spreadsheet, obj, token);
}

private static ValueTask AddRangeAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet, IEnumerable<spreadcheetahDemo.Person?> objs, CancellationToken token)
{
if (spreadsheet is null)
throw new ArgumentNullException(nameof(spreadsheet));
if (objs is null)
throw new ArgumentNullException(nameof(objs));
return AddRangeAsRowsInternalAsync(spreadsheet, objs, token);
}

private static async ValueTask AddAsRowInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, spreadcheetahDemo.Person obj, CancellationToken token)
{
var cells = ArrayPool<DataCell>.Shared.Rent(2);
try
{
await AddCellsAsRowAsync(spreadsheet, obj, cells, token).ConfigureAwait(false);
}
finally
{
ArrayPool<DataCell>.Shared.Return(cells, true);
}
}

private static async ValueTask AddRangeAsRowsInternalAsync(SpreadCheetah.Spreadsheet spreadsheet, IEnumerable<spreadcheetahDemo.Person?> objs, CancellationToken token)
{
var cells = ArrayPool<DataCell>.Shared.Rent(2);
try
{
await AddEnumerableAsRowsAsync(spreadsheet, objs, cells, token).ConfigureAwait(false);
}
finally
{
ArrayPool<DataCell>.Shared.Return(cells, true);
}
}

private static async ValueTask AddEnumerableAsRowsAsync(SpreadCheetah.Spreadsheet spreadsheet, IEnumerable<spreadcheetahDemo.Person?> objs, DataCell[] cells, CancellationToken token)
{
foreach (var obj in objs)
{
await AddCellsAsRowAsync(spreadsheet, obj, cells, token).ConfigureAwait(false);
}
}

private static ValueTask AddCellsAsRowAsync(SpreadCheetah.Spreadsheet spreadsheet, spreadcheetahDemo.Person? obj, DataCell[] cells, CancellationToken token)
{
if (obj is null)
return spreadsheet.AddRowAsync(ReadOnlyMemory<DataCell>.Empty, token);

cells[0] = new DataCell(obj.FirstName);
cells[1] = new DataCell(obj.LastName);
return spreadsheet.AddRowAsync(cells.AsMemory(0, 2), token);
}
}
}

Usefull

Download Example (.NET C# )

Share spreadcheetah

https://ignatandrei.github.io/RSCG_Examples/v2/docs/spreadcheetah

In the same category (Templating) - 6 other generators

Gobie

InterceptorTemplate

Microsoft.NET.Sdk.Razor.SourceGenerators

MorrisMoxy

RazorBlade

RSCG_Templating