初めに
大規模なC#コードを書くと、「どこで値が書き換わったのか分からない」「ちょっとした副作用がバグを生む」といった悩みを抱えがちです。
PureSharpは、そうした問題に対して「参照透過性」と「不変性」をコンパイル時にチェックすることで、関数型プログラミング的な安全性をC#にもたらすライブラリです。
対象読者
C#で業務システムや中〜大規模なアプリケーションを開発している人
「ローカル変数の再代入を禁止したい」「副作用まみれのメソッドを止めたい」と感じたことがある人
関数型プログラミングの考え方を、既存のC#コードベースに少しずつ取り入れたい人
この記事を読むと分かること
PureSharpが提供するコアコンセプト(純粋性・不変性・安全な制御フロー)
Roslynアナライザーを使って、副作用や状態変更に起因するバグをコンパイル時に検出する方法
[PureMethod] 属性やアンダースコア始まりの変数ルールを使った「事実上の不変変数」の作り方
Fluent.If による安全な条件分岐の書き方
PureSharpとは何か
PureSharpは、C#で関数型プログラミングの原則を実践しやすくするためのツールセットです。
中心となるのはRoslynアナライザーで、コードを書いているその場でルール違反を検出し、コンパイルエラーとして知らせてくれます。
特に次のようなニーズを意識して設計しています。
「C#でローカル変数の再代入を禁止したい」
「再代入不可のローカル変数を簡単に扱いたい」
「副作用のある処理を純粋な処理から分離したい」
コアコンセプト
純粋性の強制 (Purity)
PureSharpでは、メソッドに [PureMethod] 属性を付けることで、「同じ入力に対して常に同じ結果を返し、副作用を持たないメソッド」であることを宣言します。
アナライザーがこのメソッド内での副作用(静的フィールドの変更、I/O、非純粋メソッド呼び出しなど)を検出し、コンパイルエラーとして報告します。
不変性の導入 (Immutability)
C#標準では、ローカル変数を「再代入禁止」にする手段は限られています(const は使える場所が限定的)。
PureSharpでは命名規則を用い、アンダースコア (_) で始まるローカル変数を「再代入してはいけない変数」として扱い、再代入をコンパイルエラーにします。
安全な制御フロー (Safe Flow)
従来の if-else 文は、分岐漏れや副作用が紛れ込みやすい構造です。
PureSharpは、チェーン形式で条件分岐を書ける Fluent.If を提供し、.Else() まで到達していない分岐チェーンをコンパイルエラーにすることで、安全な制御フローを実現します。
機能概要
PureSharpは主に次の2コンポーネントで構成されています。
PureSharp.Core
[PureMethod] 属性(純粋メソッドの宣言)
Fluent.If などのランタイムユーティリティ
PureSharp.Analyzers
Roslynアナライザーとして動作し、以下のようなルールを提供します。
純粋メソッド内での副作用検知(RTxxxx 系ルール)
アンダースコアで始まるローカル変数の再代入禁止・宣言時初期化強制(LVPxxxx 系ルール)
Fluent.If チェーンが .Else() で正しく終端されているかのチェック(FIFxxxx 系ルール)
使い始める手順
NuGetパッケージの導入
プロジェクトにPureSharpを追加します。
dotnet add package loach.PureSharp
純粋メソッドを [PureMethod] で宣言
副作用を持たせたくないメソッドに [PureMethod] を付けます。
using PureSharp.Core;
using System;
public class Calculator
{
private static int _globalCache;
[PureMethod]
public int Add(int a, int b)
{
// OK: 純粋な計算
return a + b;
// NG: 静的フィールドへの書き込みはエラー (RT0001)
// _globalCache = a + b;
// NG: Console.WriteLine などの I/O もエラー (RT0003)
// Console.WriteLine($"Adding {a} and {b}");
}
}
ローカル変数を「事実上 immutable」にする
アンダースコアで始まるローカル変数名を使うだけで、その変数への再代入が禁止されます。
public void ProcessData()
{
// 不変変数として扱われる
int _result = CalculateValue();
// NG: 再代入すると LVP0001 エラー
// _result = 100;
int counter = 0; // 通常の可変変数
counter++;
// もし counter が一度も変更されなければ、
// 「_counter にしたほうがよい」という警告が出る場合があります (LVP0003)。
}
private int CalculateValue()
{
return 50;
}
これにより、「c# ローカル変数 再代入禁止」「再代入不可」といった要件を、言語仕様ではなくアナライザーと命名規則の組み合わせで実現できます。
Fluent.If で安全な条件分岐
Fluent.If を使うと、if-else を式として書けるうえ、終端漏れをアナライザーが検出してくれます。
int score = 75;
int status;
// Fluent.If による条件分岐
status = Fluent.If(score >= 80, () => 1)
.ElseIf(score >= 60, () => 2)
.Else(0); // .Else を忘れると FIF0001 エラー
Console.WriteLine($"Status: {status}"); // 出力: Status: 2
.Else() で必ず終端する必要があるため、「どのパスでも必ず値が返る」ことが保証され、null や未定義の値が紛れ込む余地を減らせます。
なぜこのライブラリを作ったか
C#の柔軟さは大きな魅力ですが、同時に「気づかないうちに状態を書き換えていた」「ローカル変数を再利用した結果バグになった」といった問題も招きがちです。
PureSharpは、その柔軟さに「ちょうどいい制約」を加えることで、大規模なC#コードでも関数型プログラミングに近い安全性を確保しようとする試みです。
「すべてを関数型に書き換える」のではなく、「まずは副作用を明示し、不変にしたい変数を守る」ことから始めたい方に、ちょうどよいアプローチになればと思っています。
おわりに
PureSharpはまだ発展途上のライブラリですが、現場での「C# でもう少し関数型っぽく安全に書きたい」というニーズに応えられるよう、今後もアナライザーのルールやユーティリティの拡充を検討しています。
興味があれば、ぜひ手元のプロジェクトに導入して試してみてください。
元記事: