DEV Community

MySheet: the Excel formula engine I learned to audit as fast as I wrote it

What MySheet is

MySheet is a spreadsheet formula engine for .NET. It loads an .xlsx file, re-evaluates the formulas in memory, and hands back the results — no Excel installed, no COM automation, no shelling out.

It doesn't compete with ClosedXML, EPPlus, or NPOI — those libraries remain the right tool for manipulating an Excel file with full fidelity (MySheet's own test suite uses ClosedXML as an independent oracle). MySheet solves a narrower problem:

You keep an Excel workbook on a server as the source of truth for a calculation. You need to load it, re-evaluate it with fresh inputs, and expose or write back the results — repeatedly, with low overhead, and without Excel installed anywhere.

var workbook = new Workbook();
var sheet = workbook.Sheets.Add("Sheet1");

sheet["A1"] = new NumberValue(1);
sheet["A2"] = new NumberValue(2);
sheet["A3"] = ExpressionParser.Parse("=SUM(A1:A2)", sheet);

ComputedValue result = workbook.GetCellValue("Sheet1", "A3");
double total = result.ToDouble(); // 3.0
Enter fullscreen mode Exit fullscreen mode

304 of Excel's ~520 catalogued functions are implemented. Not all 520 — and the README is upfront about it. Cell styling and number formatting are deliberately out of scope: that's not the problem this project set out to solve.

The first number that caught my eye: velocity

I went back through the changelog to write this post and noticed something I hadn't consciously registered: between v0.1.0 and v3.15.0, 19 straight days passed and 53 releases shipped — each one with a changelog note generated from conventional commits.

That number alone doesn't say much good — "fast" by itself isn't a virtue in engineering, it's just a cheaper way to accumulate technical debt if it isn't backed by some form of proof. What separates this from accelerated vibe-coding is what happened in parallel.

Two reorganizations that would break any API — backed by proof, not "trust me"

v2.0 reorganized ~190 public types into category namespaces (Danfma.MySheet.Expressions.Logical, .Mathematics, .Text, and so on) — a 100% compile-time break, zero behavior change. v3.0 encapsulated the Sheet's cell storage behind a single write choke point, laying the groundwork for the reverse dependency graph that came later.

Both shipped with a full migration guide (a type → namespace map, before/after using examples) — and both are covered by the same regression test: a .msgpack binary file saved by version 1.x is loaded and re-evaluated on every build, proving the serialization format stays byte-identical three major versions later. That's not a claim in the README — it's a test that breaks CI if someone gets it wrong.

// 2.x
sheet.Cells["A1"] = new NumberValue(10); // direct mutation of the public dictionary

// 3.0 — Cells became an IReadOnlyDictionary; writes go through one path
sheet["A1"] = new NumberValue(10); // delegates to the SetCell choke point
Enter fullscreen mode Exit fullscreen mode

The July audit: four reports became a phased plan, with before/after numbers

Instead of "we found and fixed some bugs," the repository documents a formal audit from 2026-07-10: four technical reports synthesized into a phased fix plan, each phase shipped as a partial release through a CI workflow, with an explicit author rule — no breaking changes; any candidate for an API break gets pushed to the end and evaluated separately.

Two performance findings, each with a counterfactual benchmark (run without the fix, run with the fix, compare):

RANK.EQ/RANK.AVG used to re-scan the entire range for every single cell — O(n²) on a 5,000-row column. They became two binary searches over the range snapshot's sorted view. The "20 to 40x faster" claim isn't left hanging: the BEFORE benchmark was reverted, run, measured (740ms), then compared against the fix (25ms) — same column, same process.

Recompiling a Regex on every evaluation (in REGEXTEST, in SEARCH's wildcard matching, in the static Criteria.WildcardMatch) became a bounded static cache with a 256-entry cap. A security bonus turned up along the way: Criteria/WildcardMatch had no timeout — a real ReDoS with chained wildcards — now it fails safe as "no match" instead of hanging.

Why this matters more than the speed itself

Nobody here wrote "trust me, it works." Every format change ships with a test that loads the old binary. Every optimization ships with a before/after benchmark right in the text of the fix. This isn't about AI writing code fast — it's about a process that accepts being audited at the same speed it ships.

A note on how this was built

MySheet was built almost entirely with Claude — Opus and Sonnet — including most of the July audit, delegated to subagents phase by phase, reviewed and committed by an "orchestrator" (which is just me, following the process I described above). The changelog, the plans, and the regression tests are what was left over afterward — and, without my planning it, they turned out to be the raw material for this post.


MySheet is open source. Full code, complete changelog, and the list of all 304 functions: github.com/danfma/my-sheet.

Top comments (0)