{"id":24854673,"url":"https://github.com/kwan3854/unity-nope","last_synced_at":"2025-07-18T01:14:49.077Z","repository":{"id":274334304,"uuid":"922559228","full_name":"kwan3854/Unity-NOPE","owner":"kwan3854","description":"The fastest functional programming library for Unity – the easiest and quickest way to handle exceptions and null values.","archived":false,"fork":false,"pushed_at":"2025-06-28T14:33:44.000Z","size":3917,"stargazers_count":26,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-04T18:58:32.820Z","etag":null,"topics":["awaitable","functional-programming","railway-oriented-programming","unitask","unity","unity-package"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kwan3854.png","metadata":{"files":{"readme":"README-CN.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-01-26T14:40:37.000Z","updated_at":"2025-06-30T17:40:45.000Z","dependencies_parsed_at":"2025-03-19T10:23:29.274Z","dependency_job_id":"1bed6ea8-8d58-421b-8c7b-ec6f08b20ff7","html_url":"https://github.com/kwan3854/Unity-NOPE","commit_stats":null,"previous_names":["kwan3854/unity-nope"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/kwan3854/Unity-NOPE","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwan3854%2FUnity-NOPE","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwan3854%2FUnity-NOPE/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwan3854%2FUnity-NOPE/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwan3854%2FUnity-NOPE/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kwan3854","download_url":"https://codeload.github.com/kwan3854/Unity-NOPE/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwan3854%2FUnity-NOPE/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265687129,"owners_count":23811219,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["awaitable","functional-programming","railway-oriented-programming","unitask","unity","unity-package"],"created_at":"2025-01-31T15:54:59.390Z","updated_at":"2025-07-18T01:14:49.066Z","avatar_url":"https://github.com/kwan3854.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![openupm](https://img.shields.io/npm/v/com.kwanjoong.nope?label=openupm\u0026registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.kwanjoong.nope/)\n[![License](https://img.shields.io/badge/License-MIT-brightgreen.svg)](LICENSE.md)\n\n\u003cdiv align=\"center\" style=\"margin: 20px 0\"\u003e\n  \u003ch3\u003e📚 文档语言\u003c/h3\u003e\n  \u003ca href=\"README.md\"\u003e\u003cimg src=\"https://img.shields.io/badge/🇺🇸_English-Available-success?style=for-the-badge\" alt=\"English\"\u003e\u003c/a\u003e\n  \u003ca href=\"README-KR.md\"\u003e\u003cimg src=\"https://img.shields.io/badge/🇰🇷_한국어-Available-success?style=for-the-badge\" alt=\"Korean\"\u003e\u003c/a\u003e\n  \u003ca href=\"README-JP.md\"\u003e\u003cimg src=\"https://img.shields.io/badge/🇯🇵_日本語-Available-success?style=for-the-badge\" alt=\"Japanese\"\u003e\u003c/a\u003e\n  \u003ca href=\"README-CN.md\"\u003e\u003cimg src=\"https://img.shields.io/badge/🇨🇳_中文-Current-blue?style=for-the-badge\" alt=\"Chinese\"\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n# NOPE (No Overused Possibly Evil Exceptions)\n\n![Image 1](Documentation~/NOPE.png)\n\n一个轻量级、**零GC分配**的函数式扩展库，为Unity设计，灵感来源于**CSharpFunctionalExtensions**。  \n专注于通过`Result\u003cT,E\u003e`和`Maybe\u003cT\u003e`类型来**明确处理成功/失败**而不抛出异常，同时处理**可选值**而不使用null。\n\n- **同时支持同步和异步**工作流:\n    - 如果安装了`Cysharp.Threading.Tasks`并设置了`NOPE_UNITASK`符号，则可与**UniTask**无缝集成。\n    - 如果使用**Unity6+**并设置了`NOPE_AWAITABLE`符号，则可使用内置的**Awaitable**功能。\n- 为`Result\u003cT,E\u003e`和`Maybe\u003cT\u003e`提供**完整的同步 ↔ 异步桥接**:  \n  Map/Bind/Tap/Match/Finally现在支持**\"所有组合\"**（同步→异步、异步→同步、异步→异步）。\n- **极低GC压力**: 实现为`readonly struct`以减少内存分配。\n\n\u003e **定义符号**使用说明:  \n\u003e \\- 如果想使用基于UniTask的异步功能，在**项目设置**中定义**`NOPE_UNITASK`**。  \n\u003e \\- 如果使用**Unity6+**并想要内置Awaitable集成，则定义**`NOPE_AWAITABLE`**。  \n\u003e \\- 如果只计划使用同步方法，可以不添加这两个定义符号。\n\n---\n\n## 目录\n\n1. [设计理念](#设计理念)\n2. [性能对比](#性能对比)\n3. [安装方法](#安装方法)\n4. [示例项目](#示例项目)\n5. [快速对比](#快速对比)\n6. [功能概览](#功能概览)\n7. [Result\\\u003cT,E\\\u003e使用指南](#resultte使用指南)\n    - [创建Result](#1-创建result)\n    - [Combine / CombineValues](#2-combine--combinevalues)\n    - [SuccessIf, FailureIf, Of](#3-successif-failureif-of)\n    - [Bind, Map, MapError, Tap, Ensure, Match, Finally](#4-bind-map-maperror-tap-ensure-match-finally)\n8. [Maybe\\\u003cT\\\u003e使用指南](#maybet使用指南)\n    - [创建Maybe](#1-创建maybe)\n    - [核心Maybe方法](#2-核心maybe方法)\n    - [集合辅助方法](#3-集合辅助方法)\n    - [LINQ集成](#4-linq集成)\n9. [异步支持](#异步支持)\n    - [NOPE_UNITASK 与 NOPE_AWAITABLE](#nope_unitask-与-nope_awaitable)\n    - [同步 ↔ 异步桥接](#同步--异步桥接)\n10. [使用示例](#使用示例)\n11. [API参考](#api参考)\n12. [许可证](#许可证)\n\n---\n\n## 设计理念\n\n**NOPE**旨在消除代码中的**隐式`null`检查**和**隐藏的异常**。我们使用:\n- **Result\\\u003cT,E\\\u003e** 用于**明确表示成功/失败**。\n- **Maybe\\\u003cT\\\u003e** 用于处理可选值，类似于\"可为空但不会导致空指针异常\"的类型。\n\n通过这种方式，你可以使用**简洁、函数式的风格**链式调用安全的转换方法（`Map`、`Bind`、`Tap`）或处理结果（`Match`、`Finally`）。\n\n**目标**：让复杂代码更**易读**、更安全，并使错误处理更加明确。  \n**理念**：没有隐藏的异常或`null`带来的意外。明确返回\"**失败**\"或\"**无值**\"状态，可以选择是否使用自定义错误类型。\n\n---\n\n## 性能对比\n以下性能测试是在全面使用NOPE库功能的环境中进行的。测试包括与`CSharpFunctionalExtensions`、`Optional`、`LanguageExt`和`OneOf`等库的比较。\n\n\u003e 请注意，并非所有库都提供完全相同的功能。在某些情况下，我们比较了从用户角度看效果相似的功能。\n\n![Image 2](Documentation~/Bench_Memory_250129.svg)\n![Image 1](Documentation~/Bench_Time_250129.svg)\n\n\n## 安装方法\n\n1. **通过Git (UPM)**:  \n   在`Packages/manifest.json`中添加:\n   ```json\n   {\n     \"dependencies\": {\n       \"com.kwanjoong.nope\": \"https://github.com/kwan3854/Unity-NOPE.git?path=/Packages/Unity-NOPE\"\n     }\n   }\n   ```\n   指定版本可以使用:\n   ```json\n    {\n      \"dependencies\": {\n        \"com.kwanjoong.nope\": \"https://github.com/kwan3854/Unity-NOPE.git?path=/Packages/Unity-NOPE#1.3.2\"\n      }\n    }\n   ```\n2. **Unity包管理器 (Git)**:\n    1) 打开`Window → Package Manager`\n    2) 点击\"+\" → \"Add package from git URL…\"\n    3) 粘贴 `https://github.com/kwan3854/Unity-NOPE.git?path=/Packages/Unity-NOPE`，若要指定版本，附加版本标签如 `https://github.com/kwan3854/Unity-NOPE.git?path=/Packages/Unity-NOPE#1.3.2`。\n\n3. **OpenUPM**:  \n   在命令行中运行 `openupm add com.kwanjoong.nope`。\n3. **手动下载**:  \n   克隆或下载仓库，然后放置在`Packages/`或`Assets/Plugins`文件夹中。\n\n\u003e [!NOTE] \n\u003e **定义符号说明**:\n\u003e - 使用`NOPE_UNITASK`启用**UniTask**集成\n\u003e - 使用`NOPE_AWAITABLE`启用Unity6+内置的**Awaitable**集成\n\u003e - 如果只需要同步功能，可以不添加任何定义符号\n\u003e - *不要同时定义两者*\n\n---\n\n## 示例项目\n\n本仓库包含一个演示NOPE库实际应用的Unity示例项目。使用示例项目的步骤:\n\n1. 克隆整个仓库:\n   ```bash\n   git clone https://github.com/kwan3854/Unity-NOPE.git\n   ```\n2. 在Unity中打开克隆的仓库（仓库本身就是一个Unity项目）。\n3. 在Unity编辑器中，导航并打开位于`Assets/NOPE_Examples/Scene/`的示例场景。\n4. 运行示例场景，体验NOPE库的各种功能。\n5. 学习`Assets/NOPE_Examples/Scripts/`中的示例代码。\n\n## 快速对比\n\n**想象**一个函数，它需要检查几个条件，异步获取数据，确保数据有效，然后返回成功结果或记录错误。\n\n### 不使用NOPE\n\n```csharp\npublic async Task\u003cstring\u003e DoStuff()\n{\n    // a) 检查条件\n    if (!CheckA()) \n        throw new Exception(\"Condition A failed!\");\n\n    // b) 获取数据\n    var data = await FetchData(); // 可能返回null？\n    if (data == null)\n        return null; // 不够明确\n\n    // c) 解析和验证\n    var parsed = Parse(data);\n    if (parsed \u003c= 0)\n        return \"Negative value?\";\n\n    // d) 执行最后一步\n    if (!await FinalStep(parsed))\n        return \"Final step failed!\";\n    \n    return \"All Good!\";\n}\n```\n**问题**: 混合了异常抛出、`null`和特殊字符串返回值。容易遗漏检查或意外跳过错误处理。\n\n### 使用NOPE\n\n```csharp\npublic async UniTask\u003cResult\u003cstring, string\u003e\u003e DoStuff()\n{\n    return await Result.SuccessIf(CheckA(), Unit.Value, \"Condition A failed!\")\n        .Bind(_ =\u003e  FetchData()\n            .Map(data =\u003e Parse(data))\n            .Ensure(x =\u003e x \u003e 0, \"Parsed \u003c= 0?\"))\n        .Bind(parsed =\u003e FinalStep(parsed)\n            .Map(success =\u003e success \n                ? \"All Good!\" \n                : \"Final step failed!\"));\n}\n```\n\n使用NOPE后，每一步都返回`Result\u003cT\u003e`，我们通过**Bind/Map/Ensure**在**一个链**中统一处理成功/失败。没有`null`或抛出的异常。\n\n---\n\n## 功能概览\n\n- **Result\u003cT,E\u003e**\n    - 链式方法: `Map`、`Bind`、`Tap`、`Ensure`、`MapError`、`Match`、`Finally`\n    - 使用`Combine`(无值)或`CombineValues`(生成新元组/数组)组合多个结果\n\n- **Maybe\u003cT\u003e**\n    - \"可选\"类型，无需使用`null`\n    - 提供`Map`、`Bind`、`Tap`、`Match`、`Where`、`Execute`等方法\n    - 集成LINQ接口(`Select`、`SelectMany`、`Where`)\n\n- **同步 ↔ 异步桥接**\n    - 对于每个方法(`Bind`、`Map`等)，提供:\n        - 同步→同步、同步→异步、异步→同步、异步→异步的转换\n    - 与**UniTask**(`NOPE_UNITASK`)或**Awaitable**(`NOPE_AWAITABLE`)集成\n    - 可以在单个链中无缝混合同步和异步操作\n\n- **集合工具**\n    - 针对`Maybe\u003cT\u003e`提供: `TryFind`、`TryFirst`、`TryLast`、`Choose`等\n\n---\n\n## Result\\\u003cT,E\\\u003e使用指南\n\n### 1) 创建Result\n\n```csharp\n// 基本的成功/失败创建\nvar r1 = Result\u003cint, string\u003e.Success(100);\nvar r2 = Result\u003cint, string\u003e.Failure(\"Oops\"); \n\n// 隐式转换\nResult\u003cint, string\u003e r3 = 10;  // 成功\nAssert.IsTrue(r3.IsSuccess);\nAssert.AreEqual(10, r3.Value);\n\nResult\u003cint, string\u003e r4 = \"Error\";  // 失败\nAssert.IsTrue(r4.IsFailure);\nAssert.AreEqual(\"Error\", r4.Error);\n\nvar a = 100;\nvar b = 200;\nResult\u003cint, string\u003e r5 = b == 0 ?\n    \"除数不能为零\"  // 失败时的错误消息\n    : 100;  // 成功时的值\nAssert.IsTrue(r5.IsSuccess);\nAssert.AreEqual(100, r5.Value);\n\n// 使用自定义错误类型E:\nvar r6 = Result\u003cint, SomeErrorEnum\u003e.Failure(SomeErrorEnum.FileNotFound);\n```\n\n### 2) Combine / CombineValues\n\n1. **`Combine`**\n    - 将多个`Result\u003cT,E\u003e`合并为单个**\"无值\"**的`Result\u003cUnit, E\u003e`（仅表示成功/失败）。\n    - 如果**全部**成功 → 返回Success()。如果**任一**失败 → 返回第一个错误。\n\n   ```csharp\n    var r1 = Result\u003cint, string\u003e.Success(2);\n    var r2 = Result\u003cint, string\u003e.Success(3);\n    var combined = Result.Combine(r1, r2);\n    \n    Assert.IsTrue(combined.IsSuccess);\n    Assert.AreEqual(Unit.Value, combined.Value);\n    \n    var r3 = Result\u003cint, string\u003e.Failure(\"Fail\");\n    var combined2 = Result.Combine(r1, r3);\n    Assert.IsTrue(combined2.IsFailure);\n    Assert.AreEqual(\"Fail\", combined2.Error);\n   ```\n\n2. **`CombineValues`**\n    - 将多个`Result\u003cT,E\u003e`合并为单个`Result\u003c(T1,T2,...) , E\u003e`或`Result\u003cT[], E\u003e`。\n    - 如果有任何一个失败，返回该错误。否则，返回合并后的新值。\n\n   ```csharp\n    var r1 = Result\u003cint, string\u003e.Success(2);\n    var r2 = Result\u003cint, string\u003e.Success(3);\n    var r3 = Result\u003cint, string\u003e.Failure(\"Fail\");\n   \n    // 合并两个结果为元组\n    var combinedTuple = Result.CombineValues(r1, r2);\n    Assert.IsTrue(combinedTuple.IsSuccess);\n    Assert.AreEqual((2, 3), combinedTuple.Value);\n   \n    // 合并三个结果为数组\n    var combinedArray = Result.CombineValues(r1, r2, r3);\n    Assert.IsTrue(combinedArray.IsFailure);\n    Assert.AreEqual(\"Fail\", combinedArray.Error)\n   ```\n\n### 3) SuccessIf, FailureIf, Of\n\n- **`SuccessIf(condition, successValue, error)`**  \n  → \"条件为真时返回成功，否则返回失败\"\n- **`FailureIf(condition, successValue, error)`**  \n  → \"条件为真时返回失败，否则返回成功\"\n- **`Of(func, errorConverter)`**  \n  → 包装try/catch块，如果无异常则返回成功，否则转换异常并返回失败\n\n```csharp\nvar x = 10;\n\nvar r1 = Result.SuccessIf(() =\u003e x \u003e 5, x, \"值太小\");\nAssert.IsTrue(r1.IsSuccess);\n\nvar r2 = Result.FailureIf(() =\u003e x % 2 == 0, 999, \"条件失败\");\nAssert.IsTrue(r2.IsFailure);\nAssert.AreEqual(\"条件失败\", r2.Error);\n\nvar r3 = Result.Of(() =\u003e x / 0, ex =\u003e $\"{ex.Message} 附加信息\");\nAssert.IsTrue(r3.IsFailure);\nAssert.AreEqual(\"尝试除以零。附加信息\", r3.Error);\n```\n\n### 4) Bind, Map, MapError, Tap, Ensure, Match, Finally\n\n- **Bind**: 如果成功，将`Result\u003cTOriginal,E\u003e` → `Result\u003cTNew,E\u003e`，否则保持错误不变。\n  ```csharp\n  var r1 = Result\u003cint, string\u003e.Success(10);\n  var r2 = r1.Bind(x =\u003e Result\u003cstring, string\u003e.Success($\"值是 {x}\"));\n    \n  Assert.IsTrue(r2.IsSuccess);\n  Assert.AreEqual(\"值是 10\", r2.Value);\n    \n  var r3 = Result\u003cint, string\u003e.Failure(\"初始失败\");\n  var r4 = r3.Bind(x =\u003e Result\u003cstring, string\u003e.Success($\"值是 {x}\"));\n    \n  Assert.IsTrue(r4.IsFailure);\n  Assert.AreEqual(\"初始失败\", r4.Error);\n  ```\n- **Map**: 如果成功，转换**值**并返回 → `Result\u003cU,E\u003e`，不添加新错误。\n  ```csharp\n  var r1 = Result\u003cint, string\u003e.Success(10);\n  var r2 = r1.Map(x =\u003e x + 1);\n  \n  Assert.IsTrue(r2.IsSuccess);\n  Assert.AreEqual(11, r2.Value);\n  \n  var r3 = Result\u003cint, string\u003e.Failure(\"初始失败\");\n  var r4 = r3.Map(x =\u003e x + 1);\n  \n  Assert.IsTrue(r4.IsFailure);\n  Assert.AreEqual(\"初始失败\", r4.Error);\n  ```\n\u003e [!TIP]\n\u003e ## Bind 与 Map 的区别\n\u003e ### Map\n\u003e 成功时的简单值转换 (T → U)\n\u003e ```csharp\n\u003e // mapFunc:  int =\u003e string\n\u003e string mapFunc(int x) =\u003e $\"值是 {x}\";\n\u003e \n\u003e var r1 = Result\u003cint, string\u003e.Success(10);\n\u003e var r2 = r1.Map(mapFunc);\n\u003e \n\u003e // r2 : Result\u003cstring, string\u003e\n\u003e // 成功 =\u003e \"值是 10\"\n\u003e ```\n\u003e 由于`mapFunc`本身返回字符串，`Map`内部会创建`Result\u003cstring, E\u003e.Success(mapFunc(x))`。如果`mapFunc`需要产生异常或失败结果，是做不到的（只能直接抛出异常，但这违背了Result模式的初衷）。\n\u003e ### Bind\n\u003e 成功时返回另一个Result (T → Result\u003cU,E\u003e)\n\u003e ```csharp\n\u003e // bindFunc:  int =\u003e Result\u003cstring,string\u003e\n\u003e Result\u003cstring,string\u003e bindFunc(int x)\n\u003e {\n\u003e   if (x \u003e 5)\n\u003e     return Result\u003cstring,string\u003e.Success($\"值是 {x}\");\n\u003e   else\n\u003e     return Result\u003cstring,string\u003e.Failure(\"x \u003c= 5\");\n\u003e }\n\u003e \n\u003e var r3 = Result\u003cint,string\u003e.Success(10);\n\u003e var r4 = r3.Bind(bindFunc);\n\u003e \n\u003e // r4 : Result\u003cstring,string\u003e\n\u003e // 成功 =\u003e \"值是 10\"\n\u003e ```\n\u003e `bindFunc`包含直接产生\"成功或失败\"的逻辑。`Bind`的工作方式是\"如果输入成功则调用`bindFunc`并返回其结果（成功或失败）\"，\"如果输入失败则保持现有失败状态\"。\n\n- **MapError**: 只修改错误信息。\n  ```csharp\n  var r1 = Result\u003cint, string\u003e.Failure(\"初始错误\");\n  var r2 = r1.MapError(e =\u003e $\"自定义: {e}\");\n  \n  Assert.IsTrue(r2.IsFailure);\n  Assert.AreEqual(\"自定义: 初始错误\", r2.Error);\n  \n  var r3 = Result\u003cint, string\u003e.Success(10);\n  var r4 = r3.MapError(e =\u003e $\"自定义: {e}\");\n  \n  Assert.IsTrue(r4.IsSuccess);\n  Assert.AreEqual(10, r4.Value);\n  ```\n- **Tap**: 成功时执行副作用，不改变结果。\n  ```csharp\n  var r1 = Result\u003cint, string\u003e.Success(10);\n  var r2 = r1.Tap(x =\u003e Debug.Log($\"值 = {x}\"));\n  \n  Assert.IsTrue(r2.IsSuccess);\n  Assert.AreEqual(10, r2.Value);\n  \n  var r3 = Result\u003cint, string\u003e.Failure(\"初始失败\");\n  var r4 = r3.Tap(x =\u003e Debug.Log($\"值 = {x}\"));  // 不会执行\n  \n  Assert.IsTrue(r4.IsFailure);\n  Assert.AreEqual(\"初始失败\", r4.Error);\n  ```\n- **Ensure**: \"如果成功但不满足条件 =\u003e 变为失败(error)\"。\n  ```csharp\n  var r1 = Result\u003cint, string\u003e.Success(15);\n  var r2 = r1.Ensure(x =\u003e x \u003e 10, \"值太小\");\n  \n  Assert.IsTrue(r2.IsSuccess);\n  Assert.AreEqual(15, r2.Value);\n  \n  var r3 = Result\u003cint, string\u003e.Success(5);\n  var r4 = r3.Ensure(x =\u003e x \u003e 10, \"值太小\");\n  \n  Assert.IsTrue(r4.IsFailure);\n  Assert.AreEqual(\"值太小\", r4.Error);\n  ```\n- **Match**: 将`Result\u003cT,E\u003e`转换为单一结果:\n  ```csharp\n  var r1 = Result\u003cint, string\u003e.Success(10);\n  var result1 = r1.Match(\n      onSuccess: val =\u003e $\"值 = {val}\",\n      onFailure: err =\u003e $\"错误 = {err}\"\n  );\n  \n  Assert.AreEqual(\"值 = 10\", result1);\n  \n  var r2 = Result\u003cint, string\u003e.Failure(\"初始失败\");\n  var result2 = r2.Match(\n      onSuccess: val =\u003e $\"值 = {val}\",\n      onFailure: err =\u003e $\"错误 = {err}\"\n  );\n  \n  Assert.AreEqual(\"错误 = 初始失败\", result2);\n  ```\n- **Finally**: \"链式终止\"，提供最终处理函数。\n  ```csharp\n  var r1 = Result\u003cint, string\u003e.Success(10);\n  var finalString1 = r1.Finally(res =\u003e\n  {\n      // 执行副作用\n      return res.IsSuccess ? \"成功\" : $\"失败({res.Error})\";\n  });\n  \n  Assert.AreEqual(\"成功\", finalString1);\n  \n  var r2 = Result\u003cint, string\u003e.Failure(\"初始失败\");\n  var finalString2 = r2.Finally(res =\u003e\n  {\n      // 执行副作用\n      return res.IsSuccess ? \"成功\" : $\"失败({res.Error})\";\n  });\n  \n  Assert.AreEqual(\"失败(初始失败)\", finalString2);\n  ```\n- **Or**: 如果当前Result是失败，提供一个备用的Result\u003cT,E\u003e。\n  ```csharp\n  var r1 = Result\u003cint, string\u003e.Success(10);\n  var r2 = Result\u003cint, string\u003e.Success(20);\n  var result1 = r1.Or(r2);\n  \n  Assert.IsTrue(result1.IsSuccess);\n  Assert.AreEqual(10, result1.Value);  // 原始成功值\n  \n  var r3 = Result\u003cint, string\u003e.Failure(\"第一个错误\");\n  var r4 = Result\u003cint, string\u003e.Success(30);\n  var result2 = r3.Or(r4);\n  \n  Assert.IsTrue(result2.IsSuccess);\n  Assert.AreEqual(30, result2.Value);  // 备用值\n  \n  var r5 = Result\u003cint, string\u003e.Failure(\"第一个错误\");\n  var r6 = Result\u003cint, string\u003e.Failure(\"第二个错误\");\n  var result3 = r5.Or(r6);\n  \n  Assert.IsTrue(result3.IsFailure);\n  Assert.AreEqual(\"第二个错误\", result3.Error);  // 备用错误\n  ```\n- **OrElse**: 如果当前Result是失败，通过函数提供一个备用的Result\u003cT,E\u003e。\n  ```csharp\n  var r1 = Result\u003cint, string\u003e.Success(10);\n  var result1 = r1.OrElse(() =\u003e Result\u003cint, string\u003e.Success(100));\n  \n  Assert.IsTrue(result1.IsSuccess);\n  Assert.AreEqual(10, result1.Value);  // 原始值\n  \n  var r2 = Result\u003cint, string\u003e.Failure(\"错误\");\n  var result2 = r2.OrElse(() =\u003e Result\u003cint, string\u003e.Success(100));\n  \n  Assert.IsTrue(result2.IsSuccess);\n  Assert.AreEqual(100, result2.Value);  // 备用值\n  \n  // 备用函数只在需要时执行\n  var r3 = Result\u003cint, string\u003e.Success(10);\n  var executionCount = 0;\n  var result3 = r3.OrElse(() =\u003e \n  {\n      executionCount++;\n      return Result\u003cint, string\u003e.Success(100);\n  });\n  \n  Assert.AreEqual(0, executionCount);  // 未执行\n  Assert.AreEqual(10, result3.Value);\n  ```\n\n\u003e 如果定义了`NOPE_UNITASK`/`NOPE_AWAITABLE`，所有这些方法都有**同步→异步**或**异步→异步**的变体。\n\n---\n\n## Maybe\\\u003cT\\\u003e使用指南\n\n`Maybe\u003cT\u003e`表示一个可选值（类似于`Nullable\u003cT\u003e`但没有装箱和空检查的问题）。\n\n```csharp\nMaybe\u003cint\u003e m1 = 100;         // =\u003e HasValue=true\nMaybe\u003cint\u003e m2 = Maybe\u003cint\u003e.None; // =\u003e 无值\n```\n\n### 1) 创建Maybe\n\n```csharp\n// 基本创建方法\nMaybe\u003cint\u003e m1 = 100;         // =\u003e HasValue=true\nMaybe\u003cint\u003e m2 = Maybe\u003cint\u003e.None; // =\u003e 无值\n\n// 从可空类型创建\nint? nullableInt = 10;\nMaybe\u003cint?\u003e m3 = Maybe\u003cint?\u003e.From(nullableInt); // =\u003e HasValue=true\nAssert.IsTrue(m3.HasValue);\n\nnullableInt = null;\nMaybe\u003cint?\u003e m4 = Maybe\u003cint?\u003e.From(nullableInt); // =\u003e 无值\nAssert.IsFalse(m4.HasValue);\n```\n\n### 2) 核心Maybe方法\n\n- **Map**: 如果有值则转换该值。\n  ```csharp\n  Maybe\u003cint\u003e m1 = 10;\n  Maybe\u003cstring\u003e m2 = m1.Map(x =\u003e $\"值是 {x}\");\n  \n  Assert.IsTrue(m2.HasValue);\n  Assert.AreEqual(\"值是 10\", m2.Value);\n  \n  Maybe\u003cint\u003e m3 = Maybe\u003cint\u003e.None;\n  Maybe\u003cstring\u003e m4 = m3.Map(x =\u003e $\"值是 {x}\");\n  \n  Assert.IsFalse(m4.HasValue);\n  ```\n\n- **Bind**: 将值转换为另一个`Maybe\u003cT\u003e`。\n  ```csharp\n  Maybe\u003cint\u003e m1 = 10;\n  Maybe\u003cstring\u003e m2 = m1.Bind(x =\u003e Maybe\u003cstring\u003e.From($\"值是 {x}\"));\n  \n  Assert.IsTrue(m2.HasValue);\n  Assert.AreEqual(\"值是 10\", m2.Value);\n  \n  Maybe\u003cint\u003e m3 = Maybe\u003cint\u003e.None;\n  Maybe\u003cstring\u003e m4 = m3.Bind(x =\u003e Maybe\u003cstring\u003e.From($\"值是 {x}\"));\n  \n  Assert.IsFalse(m4.HasValue);\n  ```\n\n- **Tap**: 如果有值则执行副作用，不改变原值。\n  ```csharp\n  Maybe\u003cint\u003e m1 = 10;\n  m1.Tap(x =\u003e Console.WriteLine($\"值 = {x}\"));\n  \n  Maybe\u003cint\u003e m2 = Maybe\u003cint\u003e.None;\n  m2.Tap(x =\u003e Console.WriteLine($\"值 = {x}\")); // 不会输出任何内容\n  ```\n\n- **Match**: 将`Maybe\u003cT\u003e`转换为单一结果。\n  ```csharp\n  Maybe\u003cint\u003e m1 = 10;\n  string result1 = m1.Match(\n      onValue: val =\u003e $\"值 = {val}\",\n      onNone: () =\u003e \"无值\"\n  );\n  \n  Assert.AreEqual(\"值 = 10\", result1);\n  \n  Maybe\u003cint\u003e m2 = Maybe\u003cint\u003e.None;\n  string result2 = m2.Match(\n      onValue: val =\u003e $\"值 = {val}\",\n      onNone: () =\u003e \"无值\"\n  );\n  \n  Assert.AreEqual(\"无值\", result2);\n  ```\n\n- **Where**: 如果`HasValue`但不满足条件，则变为None。\n  ```csharp\n  Maybe\u003cint\u003e m1 = 10;\n  Maybe\u003cint\u003e m2 = m1.Where(x =\u003e x \u003e 5);\n  \n  Assert.IsTrue(m2.HasValue);\n  \n  Maybe\u003cint\u003e m3 = 3;\n  Maybe\u003cint\u003e m4 = m3.Where(x =\u003e x \u003e 5);\n  \n  Assert.IsFalse(m4.HasValue);\n  ```\n\n- **Execute**: 如果Maybe\u003cT\u003e有值则执行操作。\n  ```csharp\n    Maybe\u003cint\u003e m1 = 10;\n    m1.Execute(val =\u003e Console.WriteLine($\"将会打印: {val}\"));\n    Assert.AreEqual(10, m1.Value);\n    \n    Maybe\u003cint\u003e m2 = Maybe\u003cint\u003e.None;\n    m2.Execute(val =\u003e Console.WriteLine($\"不会打印: {val}\"));\n    Assert.IsFalse(m2.HasValue);\n  ```\n\n- **Or**: 如果无值则提供默认值。\n  ```csharp\n    Maybe\u003cint\u003e m1 = 10;\n    Maybe\u003cint\u003e maybeValue1 = m1.Or(0);\n  \n    Assert.AreEqual(10, maybeValue1.Value);\n  \n    Maybe\u003cint\u003e m2 = Maybe\u003cint\u003e.None;\n    var maybeValue2 = m2.Or(0);\n  \n    Assert.AreEqual(0, maybeValue2.Value);\n  ```\n\n- **GetValueOrThrow**, **GetValueOrDefault**: 直接提取值。\n  ```csharp\n  Maybe\u003cint\u003e m1 = 10;\n  int value1 = m1.GetValueOrThrow(); // 有值，返回10\n  \n  Assert.AreEqual(10, value1);\n  \n  Maybe\u003cint\u003e m2 = Maybe\u003cint\u003e.None;\n  int value2 = m2.GetValueOrDefault(0); // 无值，返回提供的默认值0\n  \n  Assert.AreEqual(0, value2);\n  ```\n\n- **OrElse**: 如果无值，通过函数提供备用的Maybe\u003cT\u003e。\n  ```csharp\n  Maybe\u003cint\u003e m1 = 10;\n  Maybe\u003cint\u003e result1 = m1.OrElse(() =\u003e Maybe\u003cint\u003e.From(100));\n  \n  Assert.AreEqual(10, result1.Value);  // 原始值\n  \n  Maybe\u003cint\u003e m2 = Maybe\u003cint\u003e.None;\n  Maybe\u003cint\u003e result2 = m2.OrElse(() =\u003e Maybe\u003cint\u003e.From(100));\n  \n  Assert.AreEqual(100, result2.Value);  // 备用值\n  \n  // 当Maybe无值时也可以返回Result\u003cT,E\u003e\n  Maybe\u003cint\u003e m3 = Maybe\u003cint\u003e.None;\n  Result\u003cint, string\u003e result3 = m3.OrElse(() =\u003e \n      Result\u003cint, string\u003e.Failure(\"未找到值\"));\n  \n  Assert.IsTrue(result3.IsFailure);\n  ```\n\n- **ToResult**: 将Maybe\u003cT\u003e转换为Result\u003cT,E\u003e，无值时使用提供的错误。\n  ```csharp\n  Maybe\u003cint\u003e m1 = 10;\n  Result\u003cint, string\u003e result1 = m1.ToResult(\"无值\");\n  \n  Assert.IsTrue(result1.IsSuccess);\n  Assert.AreEqual(10, result1.Value);\n  \n  Maybe\u003cint\u003e m2 = Maybe\u003cint\u003e.None;\n  Result\u003cint, string\u003e result2 = m2.ToResult(\"无值\");\n  \n  Assert.IsTrue(result2.IsFailure);\n  Assert.AreEqual(\"无值\", result2.Error);\n  ```\n\n### 3) 集合辅助方法\n\n我们提供返回`Maybe\u003cT\u003e`的**集合**辅助方法:\n\n- `dict.TryFind(key) -\u003e Maybe\u003cTValue\u003e`\n  ```csharp\n  Dictionary\u003cstring, int\u003e dict = new() { { \"apple\", 10 }, { \"banana\", 5 } };\n  Maybe\u003cint\u003e found = dict.TryFind(\"banana\");\n  \n  Assert.IsTrue(found.HasValue);\n  Assert.AreEqual(5, found.Value);\n  \n  Maybe\u003cint\u003e notFound = dict.TryFind(\"cherry\");\n  \n  Assert.IsFalse(notFound.HasValue);\n  ```\n\n- `source.TryFirst()`, `source.TryLast()` → Maybe\u003cT\u003e\n  ```csharp\n  List\u003cint\u003e list = new() { 1, 2, 3 };\n  Maybe\u003cint\u003e first = list.TryFirst();\n  \n  Assert.IsTrue(first.HasValue);\n  Assert.AreEqual(1, first.Value);\n  \n  Maybe\u003cint\u003e last = list.TryLast();\n  \n  Assert.IsTrue(last.HasValue);\n  Assert.AreEqual(3, last.Value);\n  \n  List\u003cint\u003e emptyList = new();\n  Maybe\u003cint\u003e none = emptyList.TryFirst();\n  \n  Assert.IsFalse(none.HasValue);\n  ```\n\n- `Choose(...)`从`Maybe\u003cT\u003e`序列中过滤出有值的项目。\n  ```csharp\n  List\u003cMaybe\u003cint\u003e\u003e list = new() { Maybe\u003cint\u003e.From(1), Maybe\u003cint\u003e.None, Maybe\u003cint\u003e.From(3) };\n  List\u003cint\u003e chosen = list.Choose().ToList();\n  \n  Assert.AreEqual(2, chosen.Count);\n  Assert.AreEqual(1, chosen[0]);\n  Assert.AreEqual(3, chosen[1]);\n  ```\n\n### 4) LINQ集成\n\n我们实现了`Select`, `SelectMany`, `Where`接口，因此你可以使用LINQ查询语法:\n```csharp\nMaybe\u003cint\u003e maybeNum = 50;\nvar query =\n    from x in maybeNum\n    where x \u003e 10\n    select x * 2;\n// =\u003e Maybe(100)\n```\n\n---\n\n## 异步支持\n\n### NOPE_UNITASK 与 NOPE_AWAITABLE\n\n如果定义了**`NOPE_UNITASK`**，我们会为Map/Bind等方法添加`UniTask\u003cResult\u003cT,E\u003e\u003e` / `UniTask\u003cMaybe\u003cT\u003e\u003e`重载。  \n如果定义了**`NOPE_AWAITABLE`**(Unity6+)，我们会添加`Awaitable\u003cResult\u003cT,E\u003e\u003e` / `Awaitable\u003cMaybe\u003cT\u003e\u003e`重载。\n\n### 同步 ↔ 异步桥接\n\n```csharp\n// 同步结果 + 异步绑定函数\npublic static async UniTask\u003cResult\u003cTNew\u003e\u003e Bind\u003cT,TNew\u003e(\n   this Result\u003cT\u003e result,\n   Func\u003cT, UniTask\u003cResult\u003cTNew\u003e\u003e\u003e asyncBinder);\n\npublic static async Awaitable\u003cResult\u003cTNew\u003e\u003e Bind\u003cT,TNew\u003e(\n   this Result\u003cT\u003e result,\n   Func\u003cT, Awaitable\u003cResult\u003cTNew\u003e\u003e\u003e asyncBinder);\n```\n\n这样你可以无缝地将同步步骤链接到异步步骤。类似地，我们也提供了**异步结果 + 同步转换**的重载。\n\n---\n\n## 使用示例\n\n1. **链式处理多个检查和异步调用**，使用`Result\u003cint\u003e`:\n   ```csharp\n    public async UniTask\u003cstring\u003e ComplexOperation()\n    {\n        return await Result.SuccessIf(CheckA(), 0, \"CheckA失败!\")\n            .Bind(_ =\u003e FetchDataAsync()) // =\u003e UniTask\u003cResult\u003cstring\u003e\u003e\n            .Ensure(str =\u003e !string.IsNullOrEmpty(str), \"数据为空!\")\n            .Map(str =\u003e str.Length)\n            .Bind(FinalStepAsync)\n            .Match(\n                onSuccess: val =\u003e $\"成功: {val}\",\n                onFailure: err =\u003e $\"失败: {err}\"\n            );\n    }\n   ```\n\n2. **字典中使用Maybe**:\n   ```csharp\n   Dictionary\u003cstring,int\u003e dict = new() {\n     {\"apple\", 10}, {\"banana\", 5}\n   };\n   var found = dict.TryFind(\"banana\")\n       .Where(x =\u003e x \u003e= 5)\n       .Map(x =\u003e x*2) // =\u003e Maybe(10)\n       .Execute(value =\u003e Debug.Log(\"有值: \" + value))\n       .ExecuteNoValue(() =\u003e Debug.LogWarning(\"未找到或值为零\"));\n   \n   // found =\u003e Maybe(10)\n   ```\n\n3. **Combine / CombineValues**:\n   ```csharp\n    var r1 = Result\u003cint, string\u003e.Success(2);\n    var r2 = Result\u003cint, string\u003e.Success(3);\n    var merged = Result.CombineValues(r1, r2);\n    // =\u003e Result\u003c(int,int)\u003e.Success((2,3))\n   \n    var justCheck = Result.Combine(r1, r2);\n    // =\u003e Result.Success() 或返回第一个错误\n   ```\n\n4. **LINQ与Maybe结合使用**:\n   ```csharp\n   Maybe\u003cint\u003e maybeNum = 10;\n   var query =\n       from x in maybeNum\n       where x \u003e 5\n       select x*3;\n   // =\u003e Maybe(30)\n   ```\n\n---\n\n## API参考\n\n**Result\\\u003cT,E\\\u003e**\n- **Combine** / **CombineValues**\n- **SuccessIf**, **FailureIf**, **Of**\n- **Bind**, **Map**, **MapError**, **Tap**, **Ensure**, **Match**, **Finally**, **Or**, **OrElse**\n- **BindSafe**, **MapSafe**, **TapSafe**\n- 用于同步→异步桥接的重载\n\n**Maybe\\\u003cT\\\u003e**\n- **Map**, **Bind**, **Tap**, **Match**, **Finally**\n- **Where**, **Execute**, **Or**, **OrElse**, **ToResult**, **GetValueOrThrow**, **GetValueOrDefault**\n- 集合操作: **TryFind**, **TryFirst**, **TryLast**, **Choose**\n- LINQ操作符: **Select**, **SelectMany**, **Where**\n\n\u003e 完整API列表请查看`NOPE.Runtime.Core.Result` / `NOPE.Runtime.Core.Maybe`中的源代码文件\n\n---\n\n## 许可证\n\n**MIT**许可证  \n欢迎贡献和提交PR\n\n---","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkwan3854%2Funity-nope","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkwan3854%2Funity-nope","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkwan3854%2Funity-nope/lists"}