1.1 什么是特性切换?

特性切换(Feature Switch)是一种通过配置动态启用或禁用代码功能的开发模式。在 .NET 9 中,通过新的 FeatureSwitchAttribute 属性,开发者可以在代码中声明功能开关,实现以下核心价值: • 灰度发布:逐步向用户开放新功能,降低风险 • A/B 测试:同时运行多个功能版本,收集数据对比效果 • 紧急回滚:无需重新编译即可关闭问题功能

1.2 代码实战:定义特性开关

.NET 9 新增的核心类型

// 在 System.Runtime.Versioning 命名空间下
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
public sealed class FeatureSwitchAttribute : Attribute
{
    public string SwitchName { get; }
    public FeatureSwitchAttribute(string switchName) => SwitchName = switchName;
}

定义可切换的功能模块

public class PaymentService
{
    // 声明一个名为 "EnableNewPayment" 的特性开关
    [FeatureSwitch("EnableNewPayment")]
    public bool UseNewPaymentSystem => true; // 默认启用新支付系统

    public void ProcessPayment()
    {
        if (UseNewPaymentSystem)
        {
            // 新支付逻辑(代码可能被剪裁)
            NewPaymentProcessor.Process();
        }
        else
        {
            // 旧支付逻辑
            LegacyPaymentProcessor.Process();
        }
    }
}

二、剪裁优化(Trimming)——自动瘦身的黑科技

2.1 剪裁是什么?

剪裁是 .NET 的一项编译期优化技术,通过静态分析移除应用中未被使用的代码。.NET 9 的剪裁器(ILLink)能够识别特性开关标记,实现更精确的代码删除。

剪裁的核心价值: • 移动应用:APK/IPA 体积减少 30%~50% • 云函数:冷启动时间缩短 20%+ • 微服务:内存占用降低,适合容器化部署

2.2 启用剪裁:项目配置

修改 .csproj 文件

<PropertyGroup>
    <OutputType>Exe</OutputType>
    <!-- 启用剪裁 -->
    <PublishTrimmed>true</PublishTrimmed>
    <!-- 优化级别:推荐使用 partial 平衡安全与体积 -->
    <TrimMode>partial</TrimMode>
</PropertyGroup>

剪裁前后体积对比(以控制台应用为例):

功能模块 原始大小 剪裁后大小 节省比例
基础功能 15 MB 4.2 MB 72%
包含未使用特性 22 MB 4.5 MB 79%

三、特性切换与剪裁的深度结合

3.1 动态剪裁:根据开关状态优化代码

FeatureSwitchAttribute 标记的属性返回 false 时,剪裁器会自动移除相关代码分支。

示例场景:假设 EnableNewPayment 开关关闭
剪裁前代码

public void ProcessPayment()
{
    if (false) // 编译器优化为常量
    {
        NewPaymentProcessor.Process(); // 此分支代码被删除
    }
    else
    {
        LegacyPaymentProcessor.Process();
    }
}

实际效果: • NewPaymentProcessor 类及其依赖项完全从程序集中移除 • IL 代码中仅保留旧支付逻辑

3.2 高级模式:运行时动态切换

通过配置系统实现不重新编译即可切换功能:

步骤 1:绑定开关到配置

// 在 appsettings.json 中配置
{
    "FeatureSwitches": {
        "EnableNewPayment": false
    }
}

// 注入配置
builder.Services.Configure<FeatureSwitchOptions>(
    builder.Configuration.GetSection("FeatureSwitches"));

步骤 2:动态读取开关状态

public class PaymentService
{
    private readonly IOptionsSnapshot<FeatureSwitchOptions> _options;

    [FeatureSwitch("EnableNewPayment")]
    public bool UseNewPaymentSystem => _options.Value.EnableNewPayment;

    // 构造函数注入...
}

运行时行为: • 修改配置文件后,下次请求生效 • 剪裁器已移除禁用功能代码,无需重新部署


四、避坑指南:特性切换的最佳实践

4.1 适用场景 vs 不适用场景

推荐使用 避免使用
临时性功能(如促销活动) 核心业务逻辑
外部依赖项(如支付渠道) 高频调用的性能关键代码
实验性功能(A/B测试) 基础框架代码

4.2 常见问题解决方案

问题 1:剪裁后反射失效
👉 解决方案:在项目文件中标记需要保留的类型

<ItemGroup>
    <!-- 保留所有以 "Processor" 结尾的类型 -->
    <TrimmerRootAssembly Include="MyApp.Processors.*" />
</ItemGroup>

问题 2:开关状态不一致
👉 解决方案:添加开关验证中间件

app.Use(async (context, next) =>
{
    var switches = context.RequestServices.GetService<FeatureSwitchOptions>();
    if (switches.EnableNewPayment && !NewPaymentProcessor.IsAvailable)
    {
        throw new InvalidOperationException("新支付系统未就绪!");
    }
    await next();
});

五、实战演练:电商应用案例

5.1 场景描述

某电商平台需要在 “双11” 期间逐步启用新的推荐算法,同时保持旧系统作为备用。

5.2 代码实现

定义推荐服务开关

public class RecommendationService
{
    [FeatureSwitch("EnableNewRecommendation")]
    public bool UseNewAlgorithm => true;

    public IEnumerable<Product> GetRecommendations()
    {
        return UseNewAlgorithm 
            ? NewAIRecommender.Get() 
            : LegacyRuleBasedRecommender.Get();
    }
}

剪裁配置

<!-- 当 EnableNewRecommendation=false 时,移除新算法相关代码 -->
<ItemGroup Condition="'$(EnableNewRecommendation)' != 'true'">
    <TrimmerRemoveAssembly Include="NewAIRecommender.dll" />
</ItemGroup>

部署效果

开关状态 应用体积 内存占用 请求延迟
关闭 58 MB 120 MB 45 ms
启用 73 MB 180 MB 28 ms

六、总结

通过 .NET 9 的特性切换与剪裁优化,开发者可以实现: • 精准控制功能范围:通过代码级开关管理功能模块 • 极致的体积优化:自动删除未使用的代码分支 • 动态部署能力:无需重新编译即可调整功能

更多信息: 特性开关设计规范