GFramework/docs/zh-CN/tutorials/functional-programming.md
GeWuYou c969a9a022 docs(core): 修复文档中的泛型符号显示问题
- 修复了配置模块文档中 GetConfig、SetConfig、WatchConfig 方法的泛型符号
- 修复了 ECS 模块文档中 ArchSystemAdapter 的泛型符号显示
- 修复了函数式编程文档中 Option 和 Result 类型的泛型符号
- 修复了序列化和存储模块中各类方法的泛型符号显示
- 修正了存储模块文档中的序列化系统链接路径
- 修复了函数式编程教程中 Option 和 Result 的泛型符号显示
2026-03-07 15:51:47 +08:00

823 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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
{
/// <summary>
/// 玩家数据服务
/// </summary>
public class PlayerDataService
{
private readonly Dictionary<int, PlayerData> _players = new();
/// <summary>
/// 根据 ID 查找玩家(返回 Option
/// </summary>
public Option<PlayerData> FindPlayerById(int playerId)
{
// 使用 Option 包装可能不存在的值
return _players.TryGetValue(playerId, out var player)
? Option<PlayerData>.Some(player)
: Option<PlayerData>.None;
}
/// <summary>
/// 获取玩家名称(安全处理)
/// </summary>
public string GetPlayerName(int playerId)
{
// 使用 Match 模式匹配处理有值和无值的情况
return FindPlayerById(playerId).Match(
some: player => player.Name,
none: () => "未知玩家"
);
}
/// <summary>
/// 获取玩家等级(使用默认值)
/// </summary>
public int GetPlayerLevel(int playerId)
{
// 使用 GetOrElse 提供默认值
return FindPlayerById(playerId)
.Map(player => player.Level)
.GetOrElse(1);
}
/// <summary>
/// 查找高级玩家
/// </summary>
public Option<PlayerData> FindAdvancedPlayer(int playerId)
{
// 使用 Filter 过滤值
return FindPlayerById(playerId)
.Filter(player => player.Level >= 10);
}
/// <summary>
/// 获取玩家公会名称(链式调用)
/// </summary>
public string GetPlayerGuildName(int playerId)
{
// 使用 Bind 处理嵌套的 Option
return FindPlayerById(playerId)
.Bind(player => player.Guild) // Guild 也是 Option<Guild>
.Map(guild => guild.Name)
.GetOrElse("无公会");
}
}
/// <summary>
/// 玩家数据
/// </summary>
public class PlayerData
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Level { get; set; }
public Option<Guild> Guild { get; set; } = Option<Guild>.None;
}
/// <summary>
/// 公会数据
/// </summary>
public class Guild
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
}
```
**代码说明**
- `Option&lt;T&gt;` 明确表示值可能不存在,避免 NullReferenceException
- `Match` 强制处理两种情况,不会遗漏 null 检查
- `Map``Bind` 实现链式转换,代码更简洁
- `Filter` 可以安全地过滤值
- `GetOrElse` 提供默认值,避免空值传播
## 步骤 2使用 Result 进行错误处理
接下来,学习如何使用 Result 类型替代异常处理,实现更可控的错误管理。
```csharp
using GFramework.Core.functional;
using System.Text.Json;
namespace MyGame.Services
{
/// <summary>
/// 存档服务
/// </summary>
public class SaveService
{
private readonly string _saveDirectory = "./saves";
/// <summary>
/// 保存游戏数据
/// </summary>
public Result<string> SaveGame(GameSaveData data)
{
// 使用 Result.Try 自动捕获异常
return Result<string>.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;
});
}
/// <summary>
/// 加载游戏数据
/// </summary>
public Result<GameSaveData> LoadGame(int playerId)
{
try
{
// 查找最新的存档文件
var files = Directory.GetFiles(_saveDirectory, $"save_{playerId}_*.json");
if (files.Length == 0)
return Result<GameSaveData>.Failure("未找到存档文件");
var latestFile = files.OrderByDescending(f => f).First();
var json = File.ReadAllText(latestFile);
var data = JsonSerializer.Deserialize<GameSaveData>(json);
return data != null
? Result<GameSaveData>.Success(data)
: Result<GameSaveData>.Failure("存档数据解析失败");
}
catch (Exception ex)
{
return Result<GameSaveData>.Failure(ex);
}
}
/// <summary>
/// 保存并加载游戏(链式操作)
/// </summary>
public Result<GameSaveData> SaveAndReload(GameSaveData data)
{
// 使用 Bind 链接多个 Result 操作
return SaveGame(data)
.Bind(_ => LoadGame(data.PlayerId));
}
/// <summary>
/// 获取存档信息(使用 Match
/// </summary>
public string GetSaveInfo(int playerId)
{
return LoadGame(playerId).Match(
succ: data => $"存档加载成功: {data.PlayerName}, 等级 {data.Level}",
fail: ex => $"加载失败: {ex.Message}"
);
}
/// <summary>
/// 安全加载游戏(提供默认值)
/// </summary>
public GameSaveData LoadGameOrDefault(int playerId)
{
return LoadGame(playerId).IfFail(new GameSaveData
{
PlayerId = playerId,
PlayerName = "新玩家",
Level = 1
});
}
}
/// <summary>
/// 游戏存档数据
/// </summary>
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&lt;T&gt;` 将错误作为值返回,而不是抛出异常
- `Result.Try` 自动捕获异常并转换为 Result
- `Bind` 可以链接多个可能失败的操作
- `Match` 强制处理成功和失败两种情况
- `IfFail` 提供失败时的默认值
## 步骤 3使用管道操作组合函数
学习如何使用管道操作符构建流式的数据处理流程。
```csharp
using GFramework.Core.functional.pipe;
using GFramework.Core.functional.functions;
namespace MyGame.Systems
{
/// <summary>
/// 物品处理系统
/// </summary>
public class ItemProcessingSystem
{
/// <summary>
/// 处理物品掉落
/// </summary>
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)
});
}
/// <summary>
/// 计算掉落率
/// </summary>
private double CalculateDropRate(Enemy enemy, Player player)
{
return (enemy.Level * 0.1 + player.Luck * 0.05)
.Pipe(rate => Math.Min(rate, 1.0));
}
/// <summary>
/// 生成物品
/// </summary>
private List<Item> 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();
}
/// <summary>
/// 应用幸运加成
/// </summary>
private List<Item> ApplyLuckBonus(List<Item> 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();
}
/// <summary>
/// 按品质过滤
/// </summary>
private List<Item> FilterByQuality(List<Item> items)
{
return items
.Where(item => item.Quality >= ItemQuality.Uncommon)
.ToList();
}
/// <summary>
/// 条件处理物品
/// </summary>
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<Item> 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
{
/// <summary>
/// 任务系统
/// </summary>
public class QuestSystem
{
private readonly Dictionary<int, Quest> _quests = new();
private readonly Dictionary<int, List<int>> _playerQuests = new();
/// <summary>
/// 接受任务(完整流程)
/// </summary>
public Result<QuestAcceptResult> 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));
}
/// <summary>
/// 查找任务
/// </summary>
private Option<Quest> FindQuest(int questId)
{
return _quests.TryGetValue(questId, out var quest)
? Option<Quest>.Some(quest)
: Option<Quest>.None;
}
/// <summary>
/// 验证任务等级
/// </summary>
private Result<Quest> ValidateQuestLevel(int playerId, Quest quest)
{
var playerLevel = GetPlayerLevel(playerId);
return playerLevel >= quest.RequiredLevel
? Result<Quest>.Success(quest)
: Result<Quest>.Failure($"等级不足,需要 {quest.RequiredLevel} 级");
}
/// <summary>
/// 检查前置任务
/// </summary>
private Result<Quest> CheckPrerequisites(int playerId, Quest quest)
{
if (quest.PrerequisiteQuestIds.Count == 0)
return Result<Quest>.Success(quest);
var completedQuests = GetCompletedQuests(playerId);
var hasAllPrerequisites = quest.PrerequisiteQuestIds
.All(id => completedQuests.Contains(id));
return hasAllPrerequisites
? Result<Quest>.Success(quest)
: Result<Quest>.Failure("未完成前置任务");
}
/// <summary>
/// 检查任务槽位
/// </summary>
private Result<Quest> CheckQuestSlots(int playerId)
{
var activeQuests = GetActiveQuests(playerId);
return activeQuests.Count < 10
? Result<Quest>.Success(default!)
: Result<Quest>.Failure("任务栏已满");
}
/// <summary>
/// 添加任务到玩家
/// </summary>
private QuestAcceptResult AddQuestToPlayer(int playerId, Quest quest)
{
if (!_playerQuests.ContainsKey(playerId))
_playerQuests[playerId] = new List<int>();
_playerQuests[playerId].Add(quest.Id);
return new QuestAcceptResult
{
QuestId = quest.Id,
QuestName = quest.Name,
Description = quest.Description,
Rewards = quest.Rewards
};
}
/// <summary>
/// 发放初始奖励
/// </summary>
private QuestAcceptResult GiveInitialRewards(QuestAcceptResult result)
{
// 某些任务接受时就有奖励
if (result.Rewards.Gold > 0)
{
Console.WriteLine($"获得金币: {result.Rewards.Gold}");
}
return result;
}
/// <summary>
/// 完成任务(使用函数组合)
/// </summary>
public Result<QuestCompleteResult> 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));
}
/// <summary>
/// 验证任务所有权
/// </summary>
private Result<Quest> ValidateQuestOwnership(int playerId, Quest quest)
{
var activeQuests = GetActiveQuests(playerId);
return activeQuests.Contains(quest.Id)
? Result<Quest>.Success(quest)
: Result<Quest>.Failure("玩家未接受此任务");
}
/// <summary>
/// 验证任务目标
/// </summary>
private Result<Quest> ValidateQuestObjectives(Quest quest)
{
return quest.IsCompleted
? Result<Quest>.Success(quest)
: Result<Quest>.Failure("任务目标未完成");
}
/// <summary>
/// 从玩家移除任务
/// </summary>
private Quest RemoveQuestFromPlayer(int playerId, Quest quest)
{
if (_playerQuests.ContainsKey(playerId))
{
_playerQuests[playerId].Remove(quest.Id);
}
return quest;
}
/// <summary>
/// 计算奖励
/// </summary>
private QuestCompleteResult CalculateRewards(Quest quest)
{
return new QuestCompleteResult
{
QuestId = quest.Id,
QuestName = quest.Name,
Rewards = quest.Rewards,
BonusRewards = CalculateBonusRewards(quest)
};
}
/// <summary>
/// 计算额外奖励
/// </summary>
private QuestRewards CalculateBonusRewards(Quest quest)
{
// 根据任务难度给予额外奖励
return new QuestRewards
{
Gold = quest.Rewards.Gold / 10,
Experience = quest.Rewards.Experience / 10
};
}
/// <summary>
/// 发放奖励
/// </summary>
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<int> GetCompletedQuests(int playerId) => new();
private List<int> GetActiveQuests(int playerId) =>
_playerQuests.GetValueOrDefault(playerId, new List<int>());
}
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<int> 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) - 函数式编程最佳实践