--- title: 函数式编程实践 description: 学习如何在实际项目中使用 Option、Result 和管道操作等函数式编程特性 --- # 函数式编程实践 ## 学习目标 完成本教程后,你将能够: - 理解函数式编程的核心概念和优势 - 使用 Option 类型安全地处理可空值 - 使用 Result 类型进行优雅的错误处理 - 使用管道操作构建流式的数据处理流程 - 组合多个函数式操作实现复杂的业务逻辑 - 在实际游戏开发中应用函数式编程模式 ## 前置条件 - 已安装 GFramework.Core NuGet 包 - 了解 C# 基础语法和泛型 - 阅读过[快速开始](/zh-CN/getting-started/quick-start) - 了解 Lambda 表达式和 LINQ ## 步骤 1:使用 Option 处理可空值 首先,让我们学习如何使用 Option 类型替代传统的 null 检查,使代码更加安全和优雅。 ```csharp using GFramework.Core.functional; using GFramework.Core.functional.pipe; namespace MyGame.Services { /// /// 玩家数据服务 /// public class PlayerDataService { private readonly Dictionary _players = new(); /// /// 根据 ID 查找玩家(返回 Option) /// public Option FindPlayerById(int playerId) { // 使用 Option 包装可能不存在的值 return _players.TryGetValue(playerId, out var player) ? Option.Some(player) : Option.None; } /// /// 获取玩家名称(安全处理) /// public string GetPlayerName(int playerId) { // 使用 Match 模式匹配处理有值和无值的情况 return FindPlayerById(playerId).Match( some: player => player.Name, none: () => "未知玩家" ); } /// /// 获取玩家等级(使用默认值) /// public int GetPlayerLevel(int playerId) { // 使用 GetOrElse 提供默认值 return FindPlayerById(playerId) .Map(player => player.Level) .GetOrElse(1); } /// /// 查找高级玩家 /// public Option FindAdvancedPlayer(int playerId) { // 使用 Filter 过滤值 return FindPlayerById(playerId) .Filter(player => player.Level >= 10); } /// /// 获取玩家公会名称(链式调用) /// public string GetPlayerGuildName(int playerId) { // 使用 Bind 处理嵌套的 Option return FindPlayerById(playerId) .Bind(player => player.Guild) // Guild 也是 Option .Map(guild => guild.Name) .GetOrElse("无公会"); } } /// /// 玩家数据 /// public class PlayerData { public int Id { get; set; } public string Name { get; set; } = ""; public int Level { get; set; } public Option Guild { get; set; } = Option.None; } /// /// 公会数据 /// public class Guild { public int Id { get; set; } public string Name { get; set; } = ""; } } ``` **代码说明**: - `Option<T>` 明确表示值可能不存在,避免 NullReferenceException - `Match` 强制处理两种情况,不会遗漏 null 检查 - `Map` 和 `Bind` 实现链式转换,代码更简洁 - `Filter` 可以安全地过滤值 - `GetOrElse` 提供默认值,避免空值传播 ## 步骤 2:使用 Result 进行错误处理 接下来,学习如何使用 Result 类型替代异常处理,实现更可控的错误管理。 ```csharp using GFramework.Core.functional; using System.Text.Json; namespace MyGame.Services { /// /// 存档服务 /// public class SaveService { private readonly string _saveDirectory = "./saves"; /// /// 保存游戏数据 /// public Result SaveGame(GameSaveData data) { // 使用 Result.Try 自动捕获异常 return Result.Try(() => { // 验证数据 if (string.IsNullOrEmpty(data.PlayerName)) throw new ArgumentException("玩家名称不能为空"); // 创建保存目录 if (!Directory.Exists(_saveDirectory)) Directory.CreateDirectory(_saveDirectory); // 序列化数据 var json = JsonSerializer.Serialize(data); var fileName = $"save_{data.PlayerId}_{DateTime.Now:yyyyMMdd_HHmmss}.json"; var filePath = Path.Combine(_saveDirectory, fileName); // 写入文件 File.WriteAllText(filePath, json); return filePath; }); } /// /// 加载游戏数据 /// public Result LoadGame(int playerId) { try { // 查找最新的存档文件 var files = Directory.GetFiles(_saveDirectory, $"save_{playerId}_*.json"); if (files.Length == 0) return Result.Failure("未找到存档文件"); var latestFile = files.OrderByDescending(f => f).First(); var json = File.ReadAllText(latestFile); var data = JsonSerializer.Deserialize(json); return data != null ? Result.Success(data) : Result.Failure("存档数据解析失败"); } catch (Exception ex) { return Result.Failure(ex); } } /// /// 保存并加载游戏(链式操作) /// public Result SaveAndReload(GameSaveData data) { // 使用 Bind 链接多个 Result 操作 return SaveGame(data) .Bind(_ => LoadGame(data.PlayerId)); } /// /// 获取存档信息(使用 Match) /// public string GetSaveInfo(int playerId) { return LoadGame(playerId).Match( succ: data => $"存档加载成功: {data.PlayerName}, 等级 {data.Level}", fail: ex => $"加载失败: {ex.Message}" ); } /// /// 安全加载游戏(提供默认值) /// public GameSaveData LoadGameOrDefault(int playerId) { return LoadGame(playerId).IfFail(new GameSaveData { PlayerId = playerId, PlayerName = "新玩家", Level = 1 }); } } /// /// 游戏存档数据 /// public class GameSaveData { public int PlayerId { get; set; } public string PlayerName { get; set; } = ""; public int Level { get; set; } public int Gold { get; set; } public DateTime SaveTime { get; set; } = DateTime.Now; } } ``` **代码说明**: - `Result<T>` 将错误作为值返回,而不是抛出异常 - `Result.Try` 自动捕获异常并转换为 Result - `Bind` 可以链接多个可能失败的操作 - `Match` 强制处理成功和失败两种情况 - `IfFail` 提供失败时的默认值 ## 步骤 3:使用管道操作组合函数 学习如何使用管道操作符构建流式的数据处理流程。 ```csharp using GFramework.Core.functional.pipe; using GFramework.Core.functional.functions; namespace MyGame.Systems { /// /// 物品处理系统 /// public class ItemProcessingSystem { /// /// 处理物品掉落 /// public ItemDrop ProcessItemDrop(Enemy enemy, Player player) { // 使用管道操作构建处理流程 return enemy .Pipe(e => CalculateDropRate(e, player)) .Tap(rate => Console.WriteLine($"掉落率: {rate:P}")) .Pipe(rate => GenerateItems(rate)) .Tap(items => Console.WriteLine($"生成 {items.Count} 个物品")) .Pipe(items => ApplyLuckBonus(items, player)) .Pipe(items => FilterByQuality(items)) .Tap(items => Console.WriteLine($"过滤后剩余 {items.Count} 个物品")) .Let(items => new ItemDrop { Items = items, TotalValue = items.Sum(i => i.Value) }); } /// /// 计算掉落率 /// private double CalculateDropRate(Enemy enemy, Player player) { return (enemy.Level * 0.1 + player.Luck * 0.05) .Pipe(rate => Math.Min(rate, 1.0)); } /// /// 生成物品 /// private List GenerateItems(double dropRate) { var random = new Random(); var itemCount = random.NextDouble() < dropRate ? random.Next(1, 5) : 0; return Enumerable.Range(0, itemCount) .Select(_ => new Item { Id = random.Next(1000), Name = $"物品_{random.Next(100)}", Quality = (ItemQuality)random.Next(0, 4), Value = random.Next(10, 100) }) .ToList(); } /// /// 应用幸运加成 /// private List ApplyLuckBonus(List items, Player player) { return items .Select(item => item.Also(i => { if (player.Luck > 50) i.Quality = (ItemQuality)Math.Min((int)i.Quality + 1, 3); })) .ToList(); } /// /// 按品质过滤 /// private List FilterByQuality(List items) { return items .Where(item => item.Quality >= ItemQuality.Uncommon) .ToList(); } /// /// 条件处理物品 /// public string ProcessItemConditionally(Item item, bool isVip) { return item.PipeIf( predicate: i => isVip, ifTrue: i => $"VIP 物品: {i.Name} (价值 {i.Value * 2})", ifFalse: i => $"普通物品: {i.Name} (价值 {i.Value})" ); } } public class Enemy { public int Level { get; set; } } public class Player { public int Luck { get; set; } } public class Item { public int Id { get; set; } public string Name { get; set; } = ""; public ItemQuality Quality { get; set; } public int Value { get; set; } } public enum ItemQuality { Common, Uncommon, Rare, Epic } public class ItemDrop { public List Items { get; set; } = new(); public int TotalValue { get; set; } } } ``` **代码说明**: - `Pipe` 将值传递给函数,构建流式处理链 - `Tap` 执行副作用(如日志)但不改变值 - `Let` 在作用域内转换值 - `Also` 对值执行操作后返回原值 - `PipeIf` 根据条件选择不同的处理路径 ## 步骤 4:实现完整的数据处理流程 现在让我们结合 Option、Result 和管道操作,实现一个完整的游戏功能。 ```csharp using GFramework.Core.functional; using GFramework.Core.functional.pipe; using GFramework.Core.functional.control; namespace MyGame.Features { /// /// 任务系统 /// public class QuestSystem { private readonly Dictionary _quests = new(); private readonly Dictionary> _playerQuests = new(); /// /// 接受任务(完整流程) /// public Result AcceptQuest(int playerId, int questId) { return FindQuest(questId) // 转换 Option 为 Result .ToResult("任务不存在") // 验证任务等级要求 .Bind(quest => ValidateQuestLevel(playerId, quest)) // 检查前置任务 .Bind(quest => CheckPrerequisites(playerId, quest)) // 检查任务槽位 .Bind(quest => CheckQuestSlots(playerId)) // 添加到玩家任务列表 .Map(quest => AddQuestToPlayer(playerId, quest)) // 记录日志 .Tap(result => Console.WriteLine($"玩家 {playerId} 接受任务: {result.QuestName}")) // 发放初始奖励 .Map(result => GiveInitialRewards(result)); } /// /// 查找任务 /// private Option FindQuest(int questId) { return _quests.TryGetValue(questId, out var quest) ? Option.Some(quest) : Option.None; } /// /// 验证任务等级 /// private Result ValidateQuestLevel(int playerId, Quest quest) { var playerLevel = GetPlayerLevel(playerId); return playerLevel >= quest.RequiredLevel ? Result.Success(quest) : Result.Failure($"等级不足,需要 {quest.RequiredLevel} 级"); } /// /// 检查前置任务 /// private Result CheckPrerequisites(int playerId, Quest quest) { if (quest.PrerequisiteQuestIds.Count == 0) return Result.Success(quest); var completedQuests = GetCompletedQuests(playerId); var hasAllPrerequisites = quest.PrerequisiteQuestIds .All(id => completedQuests.Contains(id)); return hasAllPrerequisites ? Result.Success(quest) : Result.Failure("未完成前置任务"); } /// /// 检查任务槽位 /// private Result CheckQuestSlots(int playerId) { var activeQuests = GetActiveQuests(playerId); return activeQuests.Count < 10 ? Result.Success(default!) : Result.Failure("任务栏已满"); } /// /// 添加任务到玩家 /// private QuestAcceptResult AddQuestToPlayer(int playerId, Quest quest) { if (!_playerQuests.ContainsKey(playerId)) _playerQuests[playerId] = new List(); _playerQuests[playerId].Add(quest.Id); return new QuestAcceptResult { QuestId = quest.Id, QuestName = quest.Name, Description = quest.Description, Rewards = quest.Rewards }; } /// /// 发放初始奖励 /// private QuestAcceptResult GiveInitialRewards(QuestAcceptResult result) { // 某些任务接受时就有奖励 if (result.Rewards.Gold > 0) { Console.WriteLine($"获得金币: {result.Rewards.Gold}"); } return result; } /// /// 完成任务(使用函数组合) /// public Result CompleteQuest(int playerId, int questId) { return FindQuest(questId) .ToResult("任务不存在") .Bind(quest => ValidateQuestOwnership(playerId, quest)) .Bind(quest => ValidateQuestObjectives(quest)) .Map(quest => RemoveQuestFromPlayer(playerId, quest)) .Map(quest => CalculateRewards(quest)) .Tap(result => Console.WriteLine($"任务完成: {result.QuestName}")) .Map(result => GiveRewards(playerId, result)); } /// /// 验证任务所有权 /// private Result ValidateQuestOwnership(int playerId, Quest quest) { var activeQuests = GetActiveQuests(playerId); return activeQuests.Contains(quest.Id) ? Result.Success(quest) : Result.Failure("玩家未接受此任务"); } /// /// 验证任务目标 /// private Result ValidateQuestObjectives(Quest quest) { return quest.IsCompleted ? Result.Success(quest) : Result.Failure("任务目标未完成"); } /// /// 从玩家移除任务 /// private Quest RemoveQuestFromPlayer(int playerId, Quest quest) { if (_playerQuests.ContainsKey(playerId)) { _playerQuests[playerId].Remove(quest.Id); } return quest; } /// /// 计算奖励 /// private QuestCompleteResult CalculateRewards(Quest quest) { return new QuestCompleteResult { QuestId = quest.Id, QuestName = quest.Name, Rewards = quest.Rewards, BonusRewards = CalculateBonusRewards(quest) }; } /// /// 计算额外奖励 /// private QuestRewards CalculateBonusRewards(Quest quest) { // 根据任务难度给予额外奖励 return new QuestRewards { Gold = quest.Rewards.Gold / 10, Experience = quest.Rewards.Experience / 10 }; } /// /// 发放奖励 /// private QuestCompleteResult GiveRewards(int playerId, QuestCompleteResult result) { var totalGold = result.Rewards.Gold + result.BonusRewards.Gold; var totalExp = result.Rewards.Experience + result.BonusRewards.Experience; Console.WriteLine($"获得金币: {totalGold}"); Console.WriteLine($"获得经验: {totalExp}"); return result; } // 辅助方法 private int GetPlayerLevel(int playerId) => 10; private List GetCompletedQuests(int playerId) => new(); private List GetActiveQuests(int playerId) => _playerQuests.GetValueOrDefault(playerId, new List()); } public class Quest { public int Id { get; set; } public string Name { get; set; } = ""; public string Description { get; set; } = ""; public int RequiredLevel { get; set; } public List PrerequisiteQuestIds { get; set; } = new(); public bool IsCompleted { get; set; } public QuestRewards Rewards { get; set; } = new(); } public class QuestRewards { public int Gold { get; set; } public int Experience { get; set; } } public class QuestAcceptResult { public int QuestId { get; set; } public string QuestName { get; set; } = ""; public string Description { get; set; } = ""; public QuestRewards Rewards { get; set; } = new(); } public class QuestCompleteResult { public int QuestId { get; set; } public string QuestName { get; set; } = ""; public QuestRewards Rewards { get; set; } = new(); public QuestRewards BonusRewards { get; set; } = new(); } } ``` **代码说明**: - 使用 `Option.ToResult` 将可选值转换为结果 - 使用 `Bind` 链接多个验证步骤 - 使用 `Map` 转换成功的值 - 使用 `Tap` 添加日志而不中断流程 - 每个步骤都是纯函数,易于测试和维护 ## 完整代码 ### Program.cs ```csharp using MyGame.Services; using MyGame.Systems; using MyGame.Features; class Program { static void Main(string[] args) { Console.WriteLine("=== 函数式编程实践示例 ===\n"); // 测试 Option TestOptionUsage(); Console.WriteLine(); // 测试 Result TestResultUsage(); Console.WriteLine(); // 测试管道操作 TestPipelineUsage(); Console.WriteLine(); // 测试完整流程 TestCompleteWorkflow(); Console.WriteLine("\n=== 测试完成 ==="); } static void TestOptionUsage() { Console.WriteLine("--- 测试 Option ---"); var service = new PlayerDataService(); // 测试查找存在的玩家 Console.WriteLine(service.GetPlayerName(1)); // 测试查找不存在的玩家 Console.WriteLine(service.GetPlayerName(999)); // 测试获取等级 Console.WriteLine($"玩家等级: {service.GetPlayerLevel(1)}"); } static void TestResultUsage() { Console.WriteLine("--- 测试 Result ---"); var saveService = new SaveService(); var saveData = new GameSaveData { PlayerId = 1, PlayerName = "测试玩家", Level = 10, Gold = 1000 }; // 测试保存 var saveResult = saveService.SaveGame(saveData); saveResult.Match( succ: path => Console.WriteLine($"保存成功: {path}"), fail: ex => Console.WriteLine($"保存失败: {ex.Message}") ); // 测试加载 Console.WriteLine(saveService.GetSaveInfo(1)); } static void TestPipelineUsage() { Console.WriteLine("--- 测试管道操作 ---"); var itemSystem = new ItemProcessingSystem(); var enemy = new Enemy { Level = 5 }; var player = new Player { Luck = 60 }; var drop = itemSystem.ProcessItemDrop(enemy, player); Console.WriteLine($"掉落总价值: {drop.TotalValue}"); } static void TestCompleteWorkflow() { Console.WriteLine("--- 测试完整工作流 ---"); var questSystem = new QuestSystem(); // 测试接受任务 var acceptResult = questSystem.AcceptQuest(1, 101); acceptResult.Match( succ: result => Console.WriteLine($"接受任务成功: {result.QuestName}"), fail: ex => Console.WriteLine($"接受任务失败: {ex.Message}") ); } } ``` ## 运行结果 运行程序后,你将看到类似以下的输出: ``` === 函数式编程实践示例 === --- 测试 Option --- 未知玩家 未知玩家 玩家等级: 1 --- 测试 Result --- 保存成功: ./saves/save_1_20260307_143022.json 存档加载成功: 测试玩家, 等级 10 --- 测试管道操作 --- 掉落率: 35.00% 生成 3 个物品 过滤后剩余 2 个物品 掉落总价值: 150 --- 测试完整工作流 --- 玩家 1 接受任务: 新手任务 接受任务成功: 新手任务 === 测试完成 === ``` **验证步骤**: 1. Option 正确处理了不存在的值 2. Result 成功捕获和传播错误 3. 管道操作构建了清晰的处理流程 4. 完整工作流展示了多种技术的组合使用 ## 下一步 恭喜!你已经掌握了函数式编程的核心技术。接下来可以学习: - [使用协程系统](/zh-CN/tutorials/coroutine-tutorial) - 结合函数式编程和协程 - [实现状态机](/zh-CN/tutorials/state-machine-tutorial) - 在状态机中应用函数式模式 - [资源管理最佳实践](/zh-CN/tutorials/resource-management) - 使用 Result 处理资源加载 ## 相关文档 - [扩展方法](/zh-CN/core/extensions) - 更多函数式扩展方法 - [架构组件](/zh-CN/core/architecture) - 在架构中使用函数式编程 - [最佳实践](/zh-CN/best-practices/architecture-patterns) - 函数式编程最佳实践