C# Smart Enums:高级

发布: (2026年1月2日 GMT+8 10:17)
4 min read
原文: Dev.to

Source: Dev.to

C# 智能枚举:高级封面图

问题:复制粘贴陷阱

在第 2 部分,我们已经拥有了高性能的 O(1) 字典查找。然而,如果你的应用有数十种状态类型(订单、付款、用户等),你可能会发现自己一次又一次地复制粘贴相同的字典和查找逻辑。

建议:高级通用基类

为了保持代码 DRY(Don’t Repeat Yourself),我们可以将“繁重的工作”移到一个抽象基类中。这使得你的特定状态类能够专注于纯粹定义它们的值,同时免费继承所有优化的查找逻辑。

实现使用 Curiously Recurring Template Pattern (CRTP)。它确保每个特定的 Smart Enum(如 ProductStatus)在内存中维护自己的私有字典,防止不同类型之间的数据冲突。

实现

我们定义了一个简单的 interface,确保每个值都有一个 Id,随后提供一个基类 class,为安全访问数据提供多种方式。

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

public interface ISmartEnumValue 
{ 
    int Id { get; } 
}

public abstract class SmartEnum 
    where TValue : class, ISmartEnumValue
    where TSelf : SmartEnum
{
    private static readonly Dictionary _lookup = new();

    protected static TValue Register(TValue value)
    {
        _lookup[value.Id] = value;
        return value;
    }

    // Forces the static constructor of the child class to run immediately
    public static void Initialize() 
    {
        RuntimeHelpers.RunClassConstructor(typeof(TSelf).TypeHandle);
    }

    // 1. Strict Get: Throws if ID is missing (Use when existence is mandatory)
    public static TValue Get(int id)
    {
        if (_lookup.TryGetValue(id, out var value))
            return value;

        throw new KeyNotFoundException(
            $"Value with ID {id} not found in {typeof(TSelf).Name}");
    }

    // 2. Safe Get: Returns null if ID is missing (Use for optional data)
    public static TValue? GetOrDefault(int id) => _lookup.GetValueOrDefault(id);

    // 3. Pattern Matching Get: Returns bool (Standard .NET 'Try' pattern)
    public static bool TryGet(int id, out TValue? value)
    {
        return _lookup.TryGetValue(id, out value);
    }

    public static bool Exists(int id) => _lookup.ContainsKey(id);
    public static IEnumerable GetAll() => _lookup.Values;
}

使用示例

初始化后,您可以完全灵活地使用您的 Smart Enums。

// Initialize once at startup (e.g., Program.cs)
ProductStatus.Initialize();

// Example 1: Strict access (expects ID to exist)
var status = ProductStatus.Get(1); 
Console.WriteLine(status.Description);

// Example 2: Safe access with null check
var maybeStatus = ProductStatus.GetOrDefault(99);
if (maybeStatus != null) { /* Do something */ }

// Example 3: Pattern matching for clean branching
if (ProductStatus.TryGet(2, out var foundStatus))
{
    Console.WriteLine($"Found: {foundStatus?.Description}");
}

为什么这是一个健壮的架构选择

  • 灵活的使用 – 根据业务流程,在异常、null 或布尔值之间进行选择。
  • 严格的类型安全TSelf 约束确保 ProductStatus.Get() 直接返回 ProductStatusValue,无需强制转换。
  • 无需反射 – 通过使用 Initialize(),我们避免了程序集扫描的性能开销。
  • 零样板代码 – 具体的枚举类专注于数据本身,而引擎则封装在基类中。

动手尝试

版本说明

此高级实现需要 .NET 6 或更高版本。使用 CRTP 和现代泛型约束可确保在现代 C# 环境中实现类型安全和性能。

进一步阅读与资源

Back to Blog

相关文章

阅读更多 »

C# 智能枚举:优化版

问题:“LINQ 税” 在第 1 部分中,我们用 records 替换了魔法数字。为了查找特定的状态,我们使用了 LINQ:csharp var status = Status.All.SingleOrDe...

MiniScript 2026 路线图

2026 展望 随着 2025 接近尾声,是时候展望 2026 了!MiniScript 已经八岁。许多编程语言真的进入了它们的……