mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
- 修复了配置模块文档中 GetConfig、SetConfig、WatchConfig 方法的泛型符号 - 修复了 ECS 模块文档中 ArchSystemAdapter 的泛型符号显示 - 修复了函数式编程文档中 Option 和 Result 类型的泛型符号 - 修复了序列化和存储模块中各类方法的泛型符号显示 - 修正了存储模块文档中的序列化系统链接路径 - 修复了函数式编程教程中 Option 和 Result 的泛型符号显示
24 KiB
24 KiB
title, description
| title | description |
|---|---|
| 函数式编程实践 | 学习如何在实际项目中使用 Option、Result 和管道操作等函数式编程特性 |
函数式编程实践
学习目标
完成本教程后,你将能够:
- 理解函数式编程的核心概念和优势
- 使用 Option 类型安全地处理可空值
- 使用 Result 类型进行优雅的错误处理
- 使用管道操作构建流式的数据处理流程
- 组合多个函数式操作实现复杂的业务逻辑
- 在实际游戏开发中应用函数式编程模式
前置条件
- 已安装 GFramework.Core NuGet 包
- 了解 C# 基础语法和泛型
- 阅读过快速开始
- 了解 Lambda 表达式和 LINQ
步骤 1:使用 Option 处理可空值
首先,让我们学习如何使用 Option 类型替代传统的 null 检查,使代码更加安全和优雅。
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<T>明确表示值可能不存在,避免 NullReferenceExceptionMatch强制处理两种情况,不会遗漏 null 检查Map和Bind实现链式转换,代码更简洁Filter可以安全地过滤值GetOrElse提供默认值,避免空值传播
步骤 2:使用 Result 进行错误处理
接下来,学习如何使用 Result 类型替代异常处理,实现更可控的错误管理。
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<T>将错误作为值返回,而不是抛出异常Result.Try自动捕获异常并转换为 ResultBind可以链接多个可能失败的操作Match强制处理成功和失败两种情况IfFail提供失败时的默认值
步骤 3:使用管道操作组合函数
学习如何使用管道操作符构建流式的数据处理流程。
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 和管道操作,实现一个完整的游戏功能。
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
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 接受任务: 新手任务
接受任务成功: 新手任务
=== 测试完成 ===
验证步骤:
- Option 正确处理了不存在的值
- Result 成功捕获和传播错误
- 管道操作构建了清晰的处理流程
- 完整工作流展示了多种技术的组合使用
下一步
恭喜!你已经掌握了函数式编程的核心技术。接下来可以学习: