alexyorke/PurelySharp

GitHub: alexyorke/PurelySharp

这是一个基于 Roslyn 的 C# 代码分析器,旨在通过特定的特性标记来强制执行方法的函数式纯度,从而避免副作用。

Stars: 2 | Forks: 0

# PurelySharp Analyzer 一个旨在帮助在 C# 项目中强制执行方法纯度的 Roslyn 分析器。 **注意:** 本项目目前处于**非常早期的开发阶段**。下文描述的功能反映了_目前_的实现状态。最终目标是提供详细的纯度分析,但这**尚未实现**。 ## 目标 PurelySharp 的主要目标是为开发者提供工具,以识别旨在成为函数式纯方法的方法,并标记与该意图相关的潜在问题。 ## 当前状态与功能 目前,该分析器提供以下检查: 1. **`[EnforcePure]` / `[Pure]` 特性:** 使用 `PurelySharp.Attributes.EnforcePureAttribute` 或 `PurelySharp.Attributes.PureAttribute` 来标记旨在成为纯函数的方法。该分析器将 `[Pure]` 视为 `[EnforcePure]` 的可互换简写。同时应用这两者会产生 `PS0005`(冲突)。 2. **PS0002:未验证纯度:** 如果方法被标记了纯度特性(`[EnforcePure]` 或 `[Pure]`),并且引擎根据当前规则(见下文)无法确定它是纯的,它将报告诊断 `PS0002`。 3. **PS0003:特性位置不当:** 如果纯度特性(`[EnforcePure]` 或 `[Pure]`)被应用于方法_以外_的任何声明(例如,类、结构体、字段、属性、事件、参数),分析器将报告诊断 `PS0003`。 4. **PS0004:缺少特性:** 如果方法_没有_被标记纯度特性,但分析引擎根据当前实现的规则确定它_很可能_是纯的,它将报告诊断 `PS0004` 作为建议。 5. **基本纯度分析:** 有限的分析引擎(`PurityAnalysisEngine`)尝试验证方法的纯度。它检查: - 已知的非纯操作(例如,一些 I/O、`DateTime.Now`、字段赋值)。 - 调用方法的纯度(带有循环检测的递归检查)。 - 表达式的纯度(常量、参数、`static readonly` 字段、基本运算符等)。 - 基本语句的纯度(局部声明、return、简单的表达式语句)。 **目前尚未详细实现(或计划中)的功能:** - **对方法体的全面分析。** 虽然存在基本分析,但许多 C# 特性和控制流结构尚未处理(例如,`if`、循环、`try`、`using`、`async`/`await`、LINQ、复杂赋值、大多数框架方法)。 - 报告诊断的代码修复。 - 配置选项。 - 支持其他特性(如 `[AllowSynchronization]`)。 - 对构造函数、委托、属性、索引器等的详细分析。 ## 工作原理 分析器(`PurelySharp.Analyzer`)与 C# 编译过程集成: 1. 它识别方法声明和其他符号。 2. 它检查纯度特性(`[EnforcePure]` 或 `[Pure]`)的存在和位置。 3. 如果任一纯度特性被放错位置(不在方法上),它会报告 `PS0003`。 4. 如果带有实现的纯方法上有纯度特性,它会调用 `PurityAnalysisEngine` 检查其方法体。 - 如果引擎确定该方法(根据当前规则)_不是_纯的,它会报告 `PS0002`。 5. 如果带有实现的方法上没有纯度特性,它会调用 `PurityAnalysisEngine`。 - 如果引擎确定该方法(根据当前规则)_是_纯的,它会报告 `PS0004`。 6. **内部分析(`PurityAnalysisEngine`)仍在开发中,并不涵盖所有 C# 特性或保证正确性。** ## 安装 _该分析器尚未发布。_ 一旦发布,安装步骤可能包括: 1. **特性包:** 添加对 `PurelySharp.Attributes` NuGet 包的引用。 # 示例命令(包尚不可用) dotnet add package PurelySharp.Attributes --version 2. **分析器包/VSIX:** - 将 `PurelySharp.Analyzer` NuGet 包添加到您的项目中,以进行构建时分析。 - 在 Visual Studio 中安装 `PurelySharp.Vsix` 扩展以获取实时反馈。 ## 本地构建和安装 (VSIX + NuGet) 使用提供的脚本为 Visual Studio 生成 VSIX,并为 `PurelySharp.Attributes` 生成本地 NuGet 包。 1. 先决条件 - 安装了“Visual Studio extension development”工作负载 (VSSDK) 的 Visual Studio 2022 - .NET SDK 8.0+ 2. 构建产物 # 从仓库根目录 powershell -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 # 可选:Debug 构建 powershell -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -Configuration Debug 脚本在任何失败时都会停止,并打印产物写入的位置。典型位置: - VSIX: `PurelySharp.Vsix\bin\Release\net472\PurelySharp.Vsix.vsix` - NuGet: `PurelySharp.Attributes\bin\Release\PurelySharp.Attributes..nupkg` 3. 在 Visual Studio 中安装 VSIX - 关闭 Visual Studio。 - 双击生成的 `.vsix` 并完成安装程序。 - 重新打开 Visual Studio。 4. 使用本地 NuGet 包(`PurelySharp.Attributes`) - 在 Visual Studio 中:工具 → NuGet 包管理器 → 程序包管理器设置 → 程序包源 - 添加一个指向 `PurelySharp.Attributes\bin\Release` 的新本地源 - 在您的测试项目中:管理 NuGet 包 → 选择本地源 → 安装 `PurelySharp.Attributes` 5. 更新/卸载 - 重新运行构建脚本以生成新的 VSIX/NuGet;重新安装 VSIX 以进行更新 - 在 Visual Studio 中通过 扩展 → 管理扩展 来管理已安装的扩展 ## 使用方法 1. 引用 `PurelySharp.Attributes` 项目(或包,一旦可用)。 2. 在您打算成为函数式纯的方法上添加 `[EnforcePure]` 或 `[Pure]`(来自 `PurelySharp.Attributes`)。 3. 错误地应用该特性将会看到 PS0003。 using PurelySharp.Attributes; [Pure] // PS0003: Misplaced attribute on class public class Calculator { [Pure] public int Add(int a, int b) { // PS0002: Purity Not Verified will be reported currently, // as no actual purity analysis is performed yet. return a + b; } [Pure] public int GetConstant(); // No implementation, PS0002 NOT reported. [Pure] // PS0003: Misplaced attribute on field private int _counter = 0; } 4. 在构建期间或在 IDE 中观察 `PS0002` 和 `PS0003` 诊断(如果已安装 VSIX)。 注意:诊断信息可互换地指代 `[EnforcePure]` 和 `[Pure]`。 ## 诊断 - **PS0002:未验证纯度** - **消息:** `Method '{0}' is marked [EnforcePure]/[Pure], but its body contains operations the analyzer cannot prove pure` - **严重性:** 警告 - **含义:** 该方法被标记为纯度分析,但必要的规则尚未实现。此诊断作为占位符。 - 注意:对标记有 `[EnforcePure]` 或 `[Pure]` 的方法触发。 - **PS0003:特性位置不当** - **消息:** `The [EnforcePure] attribute can only be applied to method declarations.` - **严重性:** 警告 - **含义:** 在不适用的声明类型(例如,类、结构体、字段、属性、参数)上发现了纯度特性(`[EnforcePure]` 或 `[Pure]`)。 - 注意:消息文本提到了 `[EnforcePure]`,但该规则同样适用于 `[Pure]`。 ## 构建和测试 您可以使用 .NET CLI 构建解决方案并运行测试: ``` # 构建解决方案 dotnet build PurelySharp.sln # 运行测试 (当前验证 PS0002/PS0003 行为) dotnet test PurelySharp.sln ``` ## 许可证 本项目基于 MIT 许可证授权。 ## 支持的语言特性 支持意味着_已经_实现了_某种_程度的分析,但不保证 100% 的正确性或完整性。 `[x]` = 通常受支持/已处理。 `[/]` = 受到重大限制的部分支持/处理。 `[ ]` = 未显式处理或已知被视为非纯。 ### 表达式 - [x] 字面量表达式(数字、字符串等) - [x] `nameof` 和 `typeof` 表达式(编译时解析) - [x] 标识符(局部变量、参数(`in`、`ref readonly`、值)、`static readonly` 字段) - [/] 方法调用(递归检查、循环检测、小型的已知非纯列表。未对外部库进行深入分析。) - [/] 成员访问(静态只读字段正常。实例字段、非只读静态字段、属性获取当前被视为非纯。) - [ ] 对象创建(针对不可变类型) - 假定为非纯。 - [x] 元组表达式 - [ ] Switch 表达式 (C# 8.0+) - [ ] 模式匹配 - [x] Null 合并运算符(`??`、`?.`) - [ ] 插值字符串(除非是常量,否则假定为非纯。) - [ ] 栈分配和 Span 操作 - [/] 索引和范围 (C# 8.0+) — 当端点是纯的时候,基本的范围构造被视为纯 - [x] 位移操作和基本的二元/一元运算符 - [ ] Async/await 表达式 - [ ] 不安全代码块 - [ ] 指针操作 ### 语句 - [x] 局部声明(带有纯初始化器) - [x] Return 语句(仅作为最后一条语句,需要纯表达式) - [/] 表达式语句(纯方法调用、对局部变量的赋值正常。字段/属性赋值视为非纯。) - [ ] If 语句 - [ ] Switch 语句 - [ ] Throw 语句/表达式 - 视为非纯。 - [ ] Try-catch-finally 块 - [ ] 局部函数(通过调用进行分析。) - [ ] Using 语句 - [ ] Using 声明 (C# 8.0+) - [ ] Lock 语句 - [ ] Yield 语句(迭代器方法) - [ ] Fixed 语句 ### 集合和数据结构 - [ ] 不可变集合 (System.Collections.Immutable) - 创建假定为非纯。 - [ ] 只读集合 (IReadOnly\\\* 接口) - 创建假定为非纯。 - [ ] 数组(以只读方式使用时) - 访问/创建假定为非纯。 - [x] 元组(创建) - [ ] 集合表达式 (C# 12) - [ ] 可变集合 (List, Dictionary 等) - 创建/修改假定为非纯。 - [ ] 修改集合元素 - 假定为非纯。 - [ ] 内联数组 (C# 12) ### 方法类型 - [x] 常规方法 - [x] 表达式主体方法 - [ ] 扩展方法(通过调用进行分析。) - [ ] 局部函数(通过调用进行分析。) - [ ] 抽象方法(已忽略) - [/] 递归方法(存在循环检测。) - [ ] 虚/重写方法(像常规方法一样进行分析。) - [x] 泛型方法(通过符号分析处理。) - [ ] 异步方法 - [ ] 迭代器方法 (yield return) - [ ] 不安全方法 - [ ] 运算符重载(通过调用/二元操作进行分析。) - [ ] 用户定义的转换(通过调用进行分析。) - [ ] 静态抽象/虚接口成员 (C# 11) ### 类型声明 (分析侧重于方法体,而不是类型本身的纯度) - [ ] 类 - [ ] 接口 - [ ] 结构体 - [ ] 记录 (C# 9) - [ ] 记录结构体 (C# 10) - [ ] 枚举 - [ ] 委托 - 调用假定为非纯。 - [ ] 文件局部类型 (C# 11) - [ ] 主构造函数 (C# 12) ### 成员声明 - [x] 实例方法 - [x] 静态方法 - [ ] 构造函数 - 尚未分析。 - [/] 属性(仅 get) - 当前访问被视为非纯。 - [/] 自动属性(仅 get 或仅 init) - 当前访问被视为非纯。 - [/] 字段 (readonly) - 读取 `static readonly` 是纯的。读取实例或非 readonly 静态是非纯的。赋值是非纯的。 - [ ] 事件 - [ ] 索引器 - 访问/赋值假定为非纯。 - [ ] 必成员 (C# 11) - [ ] 分部属性 (C# 13) ### 参数类型 - [x] 值类型 - [x] 引用类型(按值传递) - [ ] Ref 参数 - 视为非纯。 - [ ] Out 参数 - 视为非纯。 - [x] In 参数 - [ ] Params 数组 - [ ] Params 集合 (C# 13) - [x] 可选参数 - [ ] Lambda 表达式中的可选参数 (C# 12) - [x] Ref readonly 参数 ### 特殊特性 - [ ] LINQ 方法 - 除非是简单的常量表达式,否则假定为非纯。 - [/] 字符串操作 - 常量是纯的,方法调用遵循调用规则。 - [/] 数学操作 - 基本运算符是纯的,`System.Math` 调用遵循调用规则。 - [x] 元组操作(创建) - [ ] 转换方法 (Parse, TryParse 等) - 假定为非纯。 - [ ] I/O 操作 (File, Console 等) - 显式标记为非纯。 - [ ] 网络操作 - 显式标记为非纯。 - [ ] 线程/任务操作 - 显式标记为非纯。 - [ ] 随机数生成 - 显式标记为非纯。 - [ ] 事件订阅/调用 - 假定为非纯。 - [ ] 委托调用 - 假定为非纯。 ### 高级语言特性 - [ ] 模式匹配 - [ ] Switch 表达式 - [ ] 列表模式 (C# 11) - [ ] 顶级语句 (C# 9) - 不适用 - [x] 文件范围命名空间 (C# 10) - 隐式支持。 - [ ] 必需成员 (C# 11) - [x] 可空引用类型注释 (C# 8.0+) - 隐式支持。 - [ ] 调用方信息特性 - [ ] 源生成器 - 不适用 - [x] 分部类/方法 - 隐式支持。 - [x] 全局 using 指令 (C# 10) - 隐式支持。 - [ ] 泛型特性 (C# 11) - [x] 任意类型的类型别名 (C# 12) - 隐式支持。 - [ ] Experimental 特性 (C# 12) ### C# 11 特定特性 - [x] 扩展的 nameof 作用域 - [x] 数值 IntPtr (nint/nuint) - [x] 泛型特性 - [x] 无符号右移运算符 (>>>) - [x] 已检查的用户定义运算符 - [x] 原始字符串字面量 - [x] UTF-8 字符串字面量 - [x] 列表模式 - [x] 文件局部类型 - [x] 必需成员 - [x] 自动默认结构体 - [x] 常量字符串上的模式匹配 Span - [x] 字符串插值表达式中的换行符 - [x] ref 字段和 scoped ref - [x] 泛型数学支持 (静态 virtual/abstract 接口成员) ### C# 12 特定特性 - [~] 集合表达式 - _部分支持:仅在创建不可变集合时_ - [x] 主构造函数 - [x] 内联数组 - [ ] Lambda 表达式中的可选参数 - [x] ref readonly 参数 - [ ] 任意类型的类型别名 - [ ] Experimental 特性 - [ ] 拦截器 (预览版) ### C# 13 特定特性 - [ ] params 集合 - [ ] Lock 对象 - [x] 转义序列 \e - [ ] 方法组自然类型改进 - [ ] 对象初始化器中的隐式索引器访问 - [ ] 迭代器/async 中的 ref/unsafe - [ ] ref 结构接口 - [ ] 重载决议优先级 - [ ] 分部属性 - [ ] field 上下文关键字 (预览版) ### 字段/属性访问 - [x] Readonly 字段 - [x] Const 字段 - [x] 仅 get 属性 - [x] 仅 init 属性 (C# 9) - [x] 可变字段 - [x] 带有 set 的属性 - [x] 静态可变字段 - [x] 事件字段 - [x] Volatile 字段(由于其特殊的内存排序语义和线程安全影响,读取和写入均被视为非纯) ### 泛型和高级构造 - [x] 泛型方法 - [ ] 带有约束的泛型类型参数 - [ ] 协变和逆变 - [x] 反射 - [x] 动态类型 - [x] 不安全代码 ## 枚举操作 PurelySharp 将枚举视为纯数据类型。以下涉及枚举的操作被认为是纯的: - 访问枚举值 - 将枚举转换为其基础数值类型 - 比较枚举值 - 使用 `Enum` 类中的方法 请注意,分析器对 `Enum.TryParse()` 进行了特殊处理,尽管使用了 `out` 参数,仍将其视为纯方法。 ### 示例 ``` public enum Status { Pending, Active, Completed, Failed } public class EnumOperations { [EnforcePure] public bool IsActiveOrPending(Status status) { return status == Status.Active || status == Status.Pending; } [EnforcePure] public int GetStatusCode(Status status) { return (int)status; } [EnforcePure] public bool ParseStatus(string value, out Status status) { return Enum.TryParse(value, out status); } [EnforcePure] public Status GetStatusFromValue(int value) { return (Status)value; } } ``` ## 委托操作 PurelySharp 支持委托类型和操作。委托的纯度分析侧重于委托的创建及其调用: ### 委托纯度规则 - **委托类型定义**:定义委托类型始终是纯的。 - **委托创建**: - 从纯方法创建委托是纯的。 - 如果 Lambda 表达式的主体是纯的并且不捕获非纯状态,则创建 Lambda 表达式是纯的。 - 如果匿名方法的主体是纯的并且不捕获非纯状态,则创建匿名方法是纯的。 - **委托调用**: - 如果委托目标是纯的并且所有参数都是纯的,则调用委托是纯的。 - 如果分析器无法确定委托目标的纯度,它会保守地将调用标记为非纯。 - **委托组合**: - 如果两个委托操作数都是纯的,则组合委托(`+=`、`+`)是纯的。 - 如果两个委托操作数都是纯的,则移除委托(`-=`、`-`)是纯的。 ### 示例 ``` // Define delegate types (always pure) public delegate int Calculator(int x, int y); public delegate void Logger(string message); public class DelegateOperations { // Pure delegate field private readonly Func _adder = (x, y) => x + y; [EnforcePure] public int Add(int x, int y) { // Creating and invoking a pure lambda delegate Calculator calc = (a, b) => a + b; return calc(x, y); } [EnforcePure] public IEnumerable ProcessNumbers(IEnumerable numbers) { // Using delegates with LINQ (pure) return numbers.Where(n => n > 0) .Select(n => n * 2); } [EnforcePure] public Func GetMultiplier(int factor) { // Higher-order function returning a pure delegate return x => x * factor; } [EnforcePure] public int CombineDelegates(int x, int y) { // Combining pure delegates Func doubler = n => n * 2; Func incrementer = n => n + 1; // Combined delegate is pure if components are pure Func combined = n => incrementer(doubler(n)); return combined(x) + combined(y); } // This would generate a diagnostic [EnforcePure] public void ImpureDelegateExample() { int counter = 0; // Impure delegate - captures and modifies a local variable Action incrementCounter = () => { counter++; }; // Invoking impure delegate incrementCounter(); // Analyzer will flag this } } ``` 请注意,委托调用的分析是保守的。如果分析器无法确定委托的纯度,它会将调用标记为非纯。 ## 特性 - [x] `[EnforcePure]` - 标记一个应进行纯度分析的方法 - [x] `[AllowSynchronization]` - 在对只读对象进行同步时,允许纯方法中出现 lock 语句 ## 非纯命名空间(始终被视为非纯) - System.IO - System.Net - System.Data - System.Threading - System.Threading.Tasks - System.Diagnostics - System.Security.Cryptography - System.Runtime.InteropServices ## 非纯类型(始终被视为非纯) - Random - DateTime - File - Console - Process - Task - Thread - Timer - WebClient - HttpClient - StringBuilder - Socket - NetworkStream ## 常见非纯操作 - 修改字段或属性 - 读取或写入 volatile 字段 - 使用 Interlocked 操作 - 调用带有副作用的方法 - I/O 操作(文件、控制台、网络) - 异步操作 - 锁定(线程同步) - 事件订阅/引发 - 不安全代码和指针操作 - 创建可变集合 ## 跨框架和语言版本支持 - [x] C# 8.0+ 语言特性 - [ ] 不同的目标框架(.NET Framework、.NET Core、.NET 5+) ## 示例 ### 纯方法示例 分析器确保标记为 `[EnforcePure]` 的方法不包含非纯操作: ``` using System; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { [EnforcePure] public int PureHelperMethod(int x) { return x * 2; // Pure operation } [EnforcePure] public int TestMethod(int x) { // Call to pure method is also pure return PureHelperMethod(x) + 5; } } ``` ### 非纯方法示例 分析器检测非纯操作并报告诊断: #### 修改状态 ``` using System; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { private int _state; [EnforcePure] public int TestMethod(int value) { _state++; // Impure operation: modifies class state return _state; } } // Analyzer Error: PMA0001 - Method 'TestMethod' is marked as pure but contains impure operations ``` #### I/O 操作 ``` using System; using System.IO; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { [EnforcePure] public void TestMethod(string path) { File.WriteAllText(path, "test"); // Impure operation: performs I/O } } // Analyzer Error: PMA0001 - Method 'TestMethod' is marked as pure but contains impure operations ``` #### 控制台输出 ``` using System; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { [EnforcePure] public int TestMethod() { Console.WriteLine("Hello World"); // Impure operation: console output return 42; } } // Analyzer Error: PMA0001 - Method 'TestMethod' is marked as pure but contains impure operations ``` #### 静态字段访问 ``` using System; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { private static string sharedState = ""; [EnforcePure] public string TestMethod() { // Reading from static field is considered impure return sharedState; } } // Analyzer Error: PMA0001 - Method 'TestMethod' is marked as pure but contains impure operations ``` #### Volatile 字段访问 ``` using System; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { private volatile int _counter; [EnforcePure] public int GetCounter() { // Both reading and writing to volatile fields is considered impure // due to their special memory ordering semantics return _counter; } [EnforcePure] public void UpdateCounter(int value) { _counter = value; // Writing to volatile field is impure } } // Analyzer Error: PMA0001 - Method 'GetCounter' is marked as pure but contains impure operations // Analyzer Error: PMA0001 - Method 'UpdateCounter' is marked as pure but contains impure operations ``` #### Volatile 字段的线程同步 ``` using System; using System.Threading; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method)] public class AllowSynchronizationAttribute : Attribute { } public class TestClass { private volatile bool _initialized; private readonly object _lock = new object(); [EnforcePure] [AllowSynchronization] // Even with AllowSynchronization, volatile read is impure public void EnsureInitialized() { if (!_initialized) // Reading volatile field is impure { lock (_lock) { if (!_initialized) // Reading volatile field again is impure { Initialize(); _initialized = true; // Writing to volatile field is impure } } } } private void Initialize() { /* ... */ } } // Analyzer Error: PMA0001 - Method 'EnsureInitialized' is marked as pure but contains impure operations ``` #### 使用 Interlocked 的原子操作 ``` using System; using System.Threading; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { private volatile int _counter; [EnforcePure] public int IncrementAndGet() { // Using Interlocked with volatile fields is impure // since it modifies shared state in a thread-safe manner return Interlocked.Increment(ref _counter); } [EnforcePure] public int CompareExchange(int newValue, int comparand) { // All Interlocked operations are impure return Interlocked.CompareExchange(ref _counter, newValue, comparand); } } // Analyzer Error: PMA0001 - Method 'IncrementAndGet' is marked as pure but contains impure operations // Analyzer Error: PMA0001 - Method 'CompareExchange' is marked as pure but contains impure operations ``` ### 更复杂的示例 #### LINQ 操作(纯) LINQ 操作通常被认为是纯的,因为它们处理数据的不可变视图: ``` using System; using System.Linq; using System.Collections.Generic; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { [EnforcePure] public IEnumerable FilterAndTransform(IEnumerable numbers) { // LINQ operations are pure return numbers .Where(n => n > 10) .Select(n => n * 2) .OrderBy(n => n); } } // No analyzer errors - method is pure ``` #### 迭代器方法(纯) 使用 `yield return` 的迭代器方法可以是纯的: ``` using System; using System.Collections.Generic; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { [EnforcePure] public IEnumerable GenerateFibonacciSequence(int count) { int a = 0, b = 1; for (int i = 0; i < count; i++) { yield return a; (a, b) = (b, a + b); // Tuple deconstruction for swapping } } } // No analyzer errors - method is pure ``` #### 带有 AllowSynchronization 的 Lock 语句 当使用 `[AllowSynchronization]` 特性时,纯方法中允许出现 lock 语句: ``` using System; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method)] public class AllowSynchronizationAttribute : Attribute { } public class TestClass { private readonly object _lockObj = new object(); private readonly Dictionary _cache = new Dictionary(); [EnforcePure] [AllowSynchronization] public int GetOrComputeValue(string key, Func computeFunc) { lock (_lockObj) // Normally impure, but allowed with [AllowSynchronization] { if (_cache.TryGetValue(key, out int value)) return value; // Compute is allowed if the function is pure int newValue = computeFunc(key); _cache[key] = newValue; // This would be impure without [AllowSynchronization] return newValue; } } } // No analyzer errors with [AllowSynchronization] ``` #### Switch 表达式和模式匹配 支持现代 C# 模式匹配和 switch 表达式: ``` using System; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class Shape { } public class Circle : Shape { public double Radius { get; } } public class Rectangle : Shape { public double Width { get; } public double Height { get; } } public class TestClass { [EnforcePure] public double CalculateArea(Shape shape) { // Switch expression with pattern matching return shape switch { Circle c => Math.PI * c.Radius * c.Radius, Rectangle r => r.Width * r.Height, _ => throw new ArgumentException("Unknown shape type") }; } } // No analyzer errors - method is pure ``` #### 使用不可变集合 使用不可变集合的方法保持纯度: ``` using System; using System.Collections.Immutable; using System.Linq; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { [EnforcePure] public ImmutableDictionary AddToCounters( ImmutableDictionary counters, string key) { // Working with immutable collections preserves purity if (counters.TryGetValue(key, out int currentCount)) return counters.SetItem(key, currentCount + 1); else return counters.Add(key, 1); // Note: The original collection is not modified } } // No analyzer errors - method is pure ``` #### 包含多个纯操作的复杂方法 结合多个纯操作的复杂方法: ``` using System; using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; [AttributeUsage(AttributeTargets.Method)] public class EnforcePureAttribute : Attribute { } public class TestClass { [EnforcePure] public (double Average, int Min, int Max, ImmutableList Filtered) AnalyzeData( IEnumerable data, int threshold) { // Local function (must also be pure) bool IsOutlier(int value) => value < 0 || value > 1000; // Use LINQ to process the data var filteredData = data .Where(x => !IsOutlier(x) && x >= threshold) .ToImmutableList(); if (!filteredData.Any()) throw new ArgumentException("No valid data points after filtering"); // Multiple computations on the filtered data var average = filteredData.Average(); var min = filteredData.Min(); var max = filteredData.Max(); // Return a tuple with the results return (Average: average, Min: min, Max: max, Filtered: filteredData); } } // No analyzer errors - method is pure ``` ### 限制和边缘情况 以下示例演示了分析器可能无法正确识别非纯操作的情况: #### 间接方法非纯性 ``` ``` ## 构造函数分析 当将 `[EnforcePure]` 应用于构造函数时,分析器应用特殊规则以适应构造函数的独特目的。标记为纯的构造函数必须遵循以下规则: 1. **允许实例字段/属性赋值**:与常规方法不同,构造函数可以将值赋给包含类型的实例字段和属性。 2. **不允许修改静态字段**:修改静态字段仍被视为非纯,因为这会影响所构造实例之外的状态。 3. **不允许调用非纯方法**:不允许从纯构造函数调用非纯方法(如 I/O 操作)。 4. **允许集合初始化**:如果将集合分配给实例字段,则允许创建和初始化集合(例如,`new List { 1, 2, 3 }`)。 5. **基构造函数调用**:如果构造函数调用基构造函数,则该基构造函数也必须是纯的。 ### 示例 #### 纯构造函数 ``` [AttributeUsage(AttributeTargets.Constructor)] public class EnforcePureAttribute : Attribute { } public class Person { private readonly string _name; private readonly int _age; private readonly List _skills; [EnforcePure] public Person(string name, int age) { _name = name; _age = age; _skills = new List(); // Allowed: initializing a collection field } } ``` #### 非纯构造函数(修改静态字段) ``` public class Counter { private static int _instanceCount = 0; private readonly int _id; [EnforcePure] public Counter() // Error: Modifies static state { _id = ++_instanceCount; // Impure: modifies static field } } ``` #### 非纯构造函数(调用非纯方法) ``` public class Logger { private readonly string _name; [EnforcePure] public Logger(string name) // Error: Calls an impure method { _name = name; InitializeLog(); // Calls impure method } private void InitializeLog() { Console.WriteLine($"Logger {_name} initialized"); // Impure operation } } ``` ## 跨框架和语言版本支持 ``` ## Demo 项目 A ready-to-run demo app is included in the solution: `PurelySharp.Demo`. - What it shows - PS0002: Methods marked `[EnforcePure]` performing impure operations (state mutation, I/O, volatile reads, array mutation) - PS0004: Methods that appear pure but are missing `[EnforcePure]` - PS0003 is intentionally not demonstrated in the demo to keep the focus on core purity rules - How it’s wired - References `PurelySharp.Analyzer` and `PurelySharp.CodeFixes` as analyzers via project references - References `PurelySharp.Attributes` as a normal project reference - Local `.editorconfig` in `PurelySharp.Demo` tunes severities: PS0002=warning, PS0004=suggestion, PS0003=none - Run the demo ```powershell # Build the whole solution (ensures analyzers are built) dotnet build .\PurelySharp.sln -c Release # Build just the demo project dotnet build .\PurelySharp.Demo\PurelySharp.Demo.csproj -c Release ``` - 在 Visual Studio 中 - 安装 VSIX(见上文)以便在编辑时获取实时诊断 - 打开 `PurelySharp.Demo` 并检查 `Program.cs` 以查看内联诊断
标签:Analyzer, EnforcePure, Pure, Roslyn, SOC Prime, Visual Studio, 代码规范, 多人体追踪, 安全专业人员, 属性标记, 开发工具, 数据管道, 方法纯度, 无副作用, 纯函数, 编译器, 软件工程, 错误基检测, 静态代码分析, 静态检查