C# is a very flexible language, but that flexibility also makes it easy for side effects and mutable state to spread through a codebase.
I built PureSharp to bring a few functional-programming ideas into everyday C# development and make them enforceable at compile time.
GitHub: https://github.com/mao2009/PureSharp
NuGet: https://www.nuget.org/packages/loach.PureSharp
What PureSharp tries to enforce
PureSharp focuses on three ideas:
- Purity: methods explicitly marked as pure should not perform side effects.
- Immutability: local variables can opt into a "do not reassign" convention.
- Safer control flow: fluent conditional expressions should terminate explicitly.
The key point is that these rules are not just documentation. Roslyn analyzers report violations while you write and build the code.
Declaring a pure method
A method can be marked with [PureMethod]:
using PureSharp.Core;
public class Calculator
{
[PureMethod]
public int Add(int a, int b)
{
return a + b;
}
}
Inside such a method, PureSharp can report operations that violate the declared purity contract, such as accessing mutable static state, calling methods that are not known to be pure, or performing I/O.
The goal is not to claim that every C# method can be proven mathematically pure. The goal is more practical: if a developer explicitly says "this method should be pure," the analyzer should help keep it that way.
Opt-in immutability for local variables
C# does not have a general-purpose const-style feature for local variables initialized at runtime.
PureSharp uses a naming convention: local variables beginning with _ are treated as non-reassignable.
public void ProcessData()
{
int _result = CalculateValue();
// Reported by the analyzer:
// _result = 100;
}
This turns a simple convention into something the compiler can enforce.
The analyzer can also require immediate initialization and can suggest the immutable convention for locals that are never reassigned.
Fluent conditional expressions
PureSharp also provides Fluent.If, which makes it possible to use conditional branching as an expression:
int status = Fluent.If(score >= 80, () => 1)
.ElseIf(score >= 60, () => 2)
.Else(0);
The analyzer checks that a fluent chain is properly terminated with .Else().
The motivation is simple: if the construct is intended to produce a value, all paths should be explicit.
Why Roslyn analyzers?
These rules could be described in a style guide, but a style guide is only useful when someone remembers to enforce it.
Roslyn analyzers make the feedback immediate:
- violations appear in the IDE
-
dotnet buildcan fail on violations - CI does not need a separate custom validator
- rules stay close to the normal C# development workflow
That makes them especially useful for conventions that are easy to understand but tedious to police in code review.
Installation
Install the package from NuGet:
dotnet add package loach.PureSharp
After that, the analyzer participates in normal builds.
What I learned from building it
The interesting part of a tool like PureSharp is not the syntax of a single diagnostic. It is deciding where static analysis can give useful guarantees without pretending to understand more than it actually can.
C# remains an imperative, object-oriented language with many escape hatches. PureSharp is therefore intentionally opt-in: developers declare the rules they want, and the analyzer checks those declarations as far as static analysis reasonably allows.
That balance is what makes Roslyn analyzers attractive for this kind of developer tooling.
PureSharp is open source here:
Top comments (0)