Skip to content

.NET 10 [Embedded] 特性

学习 .NET 10 中引入的新特性:AddEmbeddedAttributeDefinition() API 和 [Embedded] 特性。

问题背景

CS0436 警告的产生

在 .NET 10 之前,源生成器通常使用 RegisterPostInitializationOutput 来生成标记特性:

csharp
context.RegisterPostInitializationOutput(ctx =>
{
    ctx.AddSource("MyAttribute.g.cs", @"
namespace MyGenerator
{
    internal sealed class MyAttribute : System.Attribute { }
}");
});

当项目使用 [InternalsVisibleTo] 时会出现问题:

csharp
[assembly: InternalsVisibleTo("MyProject.Tests")]

此时编译器会报告 CS0436 警告:

warning CS0436: The type 'MyAttribute' conflicts with the imported type 'MyAttribute'

.NET 10 解决方案

什么是 [Embedded] 特性?

[Embedded] 是一个编译器识别的特殊特性,用于标记"嵌入式"类型。当一个类型被标记为 [Embedded] 时:

  1. 不会在程序集外可见: 即使类型是 internal,也不会通过 [InternalsVisibleTo] 暴露
  2. 避免类型冲突: 每个程序集都有自己的嵌入式类型副本,互不干扰
  3. 编译器优化: 编译器会特殊处理这些类型

AddEmbeddedAttributeDefinition() API

.NET 10 引入了新的 API 来简化使用:

csharp
context.RegisterPostInitializationOutput(ctx =>
{
    // 自动生成 System.Runtime.CompilerServices.EmbeddedAttribute 定义
    ctx.AddEmbeddedAttributeDefinition();
    
    // 在生成的特性上应用 [Embedded]
    ctx.AddSource("MyAttribute.g.cs", @"
namespace MyGenerator
{
    [global::System.Runtime.CompilerServices.Embedded]
    internal sealed class MyAttribute : System.Attribute { }
}");
});

实现示例

csharp
[Generator]
public class EmbeddedAttributeGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterPostInitializationOutput(ctx =>
        {
            // 1. 添加 [Embedded] 特性定义
            ctx.AddEmbeddedAttributeDefinition();
            
            // 2. 生成标记特性,并应用 [Embedded]
            ctx.AddSource("GenerateInfoAttribute.g.cs", @"
[global::System.Runtime.CompilerServices.Embedded]
[System.AttributeUsage(System.AttributeTargets.Class)]
internal sealed class GenerateInfoAttribute : System.Attribute
{
}");
        });
        
        // 3. 使用特性查找标记的类
        var markedClasses = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "MyGenerator.GenerateInfoAttribute",
                predicate: static (node, _) => node is ClassDeclarationSyntax,
                transform: static (ctx, _) => GetClassInfo(ctx));
        
        // 4. 生成代码
        context.RegisterSourceOutput(markedClasses,
            static (spc, classInfo) => GenerateCode(classInfo, spc));
    }
}

[Embedded] vs 共享 DLL 对比

特性[Embedded] 特性共享 DLL 方案
配置复杂度低 - 只需一行代码高 - 需要额外的项目和引用
CS0436 警告✅ 完全解决✅ 完全解决
项目数量2 个3 个
MSBuild 配置简单复杂
代码清晰度
维护成本
版本要求.NET 10+.NET 5+

SDK 版本要求

最低要求

  • .NET SDK: 10.0.100 或更高
  • C# 语言版本: 12 或更高
  • Microsoft.CodeAnalysis.CSharp: 4.14.0 或更高

检查 SDK 版本

bash
dotnet --version

向后兼容性

AddEmbeddedAttributeDefinition() API 仅在 .NET 10+ 中可用。如果需要支持更早的版本:

  1. 使用共享 DLL 方案(适用于 .NET 5+)
  2. 手动生成 [Embedded] 特性(适用于 .NET 7+)
  3. 忽略 CS0436 警告(不推荐)

[Embedded] 特性的严格要求

[Embedded] 特性必须完全匹配以下定义:

csharp
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(
        AttributeTargets.Class | AttributeTargets.Struct | 
        AttributeTargets.Enum | AttributeTargets.Interface | 
        AttributeTargets.Delegate, 
        Inherited = false)]
    internal sealed class EmbeddedAttribute : Attribute
    {
    }
}

AddEmbeddedAttributeDefinition() API 会自动生成符合要求的定义,因此推荐使用这个 API。

何时使用 [Embedded]

适合使用

  • ✅ 生成标记特性(marker attributes)
  • ✅ 项目使用 [InternalsVisibleTo]
  • ✅ 需要避免 CS0436 警告
  • ✅ 使用 .NET 10 或更高版本

不适合使用

  • ❌ 需要支持 .NET 9 或更早版本
  • ❌ 特性需要在程序集外可见
  • ❌ 特性需要在多个项目间共享

常见问题

Q: 为什么需要 [Embedded] 特性?

A: 当项目使用 [InternalsVisibleTo] 时,生成的 internal 特性会在多个程序集中可见,导致类型冲突。

Q: 可以手动编写 [Embedded] 特性吗?

A: 可以,但不推荐。推荐使用 AddEmbeddedAttributeDefinition() API。

Q: [Embedded] 特性会影响性能吗?

A: 不会。[Embedded] 特性只在编译时起作用。

Q: 可以在 .NET 9 中使用 [Embedded] 吗?

A: 可以手动生成定义,但 AddEmbeddedAttributeDefinition() API 仅在 .NET 10+ 中可用。

学到的知识点

  • AddEmbeddedAttributeDefinition() API 的使用
  • [Embedded] 特性的作用和原理
  • CS0436 警告的产生原因和解决方案
  • 何时使用 [Embedded] 特性
  • 与共享 DLL 方案的对比

相关资源

完整项目结构

DotNet10Embedded/
├── DotNet10Embedded.Generator/
│   ├── DotNet10Embedded.Generator.csproj
│   ├── InfoGenerator.cs                    # 使用 [Embedded] 的生成器
│   └── Properties/
│       └── launchSettings.json
├── DotNet10Embedded.Consumer/
│   ├── DotNet10Embedded.Consumer.csproj
│   ├── Program.cs                          # 主程序
│   ├── Person.cs                           # 使用生成器的类
│   └── Product.cs                          # 另一个使用生成器的类
└── DotNet10Embedded.Tests/                 # 测试项目(可选)
    ├── DotNet10Embedded.Tests.csproj
    └── GeneratorTests.cs

详细代码实现

生成器项目配置

DotNet10Embedded.Generator.csproj:

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>12</LangVersion>
    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
    <IsRoslynComponent>true</IsRoslynComponent>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <!-- 使用最新的 Roslyn 版本以支持 AddEmbeddedAttributeDefinition -->
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
  </ItemGroup>

</Project>

完整生成器实现

InfoGenerator.cs:

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Linq;
using System.Text;

namespace DotNet10Embedded.Generator;

/// <summary>
/// 使用 .NET 10 [Embedded] 特性的源生成器
/// </summary>
[Generator]
public class InfoGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 步骤 1: 在初始化阶段生成特性定义
        context.RegisterPostInitializationOutput(ctx =>
        {
            // 使用 .NET 10 新 API 自动生成 EmbeddedAttribute 定义
            // 这会生成 System.Runtime.CompilerServices.EmbeddedAttribute
            ctx.AddEmbeddedAttributeDefinition();
            
            // 生成我们的标记特性,并应用 [Embedded]
            ctx.AddSource("GenerateInfoAttribute.g.cs", SourceText.From(@"
// <auto-generated/>
#nullable enable

namespace DotNet10Embedded.Generator
{
    /// <summary>
    /// 标记类以生成 GetInfo 方法
    /// </summary>
    [global::System.Runtime.CompilerServices.Embedded]
    [global::System.AttributeUsage(
        global::System.AttributeTargets.Class,
        Inherited = false,
        AllowMultiple = false)]
    internal sealed class GenerateInfoAttribute : global::System.Attribute
    {
        /// <summary>
        /// 是否包含类型信息
        /// </summary>
        public bool IncludeType { get; set; } = true;
        
        /// <summary>
        /// 自定义格式字符串
        /// </summary>
        public string? Format { get; set; }
    }
}
", Encoding.UTF8));
        });
        
        // 步骤 2: 使用 ForAttributeWithMetadataName 查找标记的类
        // 这是增量生成器的推荐方式,性能最优
        var classDeclarations = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                fullyQualifiedMetadataName: "DotNet10Embedded.Generator.GenerateInfoAttribute",
                predicate: static (node, _) => node is ClassDeclarationSyntax,
                transform: static (context, _) => GetClassToGenerate(context))
            .Where(static m => m is not null);
        
        // 步骤 3: 注册源代码输出
        context.RegisterSourceOutput(classDeclarations,
            static (spc, classInfo) =>
            {
                if (classInfo is null)
                    return;
                
                var source = GenerateInfoMethod(classInfo.Value);
                spc.AddSource($"{classInfo.Value.ClassName}.g.cs", SourceText.From(source, Encoding.UTF8));
            });
    }
    
    /// <summary>
    /// 从语法上下文中提取类信息
    /// </summary>
    private static ClassInfo? GetClassToGenerate(GeneratorAttributeSyntaxContext context)
    {
        // 获取类声明语法
        if (context.TargetNode is not ClassDeclarationSyntax classDeclaration)
            return null;
        
        // 获取类符号
        if (context.TargetSymbol is not INamedTypeSymbol classSymbol)
            return null;
        
        // 检查是否是 partial 类
        if (!classDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword))
        {
            // 报告诊断:类必须是 partial
            return null;
        }
        
        // 获取特性数据
        var attributeData = context.Attributes.FirstOrDefault();
        var includeType = true;
        string? format = null;
        
        if (attributeData is not null)
        {
            foreach (var namedArg in attributeData.NamedArguments)
            {
                if (namedArg.Key == "IncludeType" && namedArg.Value.Value is bool b)
                    includeType = b;
                else if (namedArg.Key == "Format" && namedArg.Value.Value is string s)
                    format = s;
            }
        }
        
        // 获取所有公共属性
        var properties = classSymbol.GetMembers()
            .OfType<IPropertySymbol>()
            .Where(p => p.DeclaredAccessibility == Accessibility.Public
                     && p.GetMethod is not null)
            .Select(p => new PropertyInfo(p.Name, p.Type.ToDisplayString()))
            .ToImmutableArray();
        
        return new ClassInfo(
            ClassName: classSymbol.Name,
            Namespace: classSymbol.ContainingNamespace.IsGlobalNamespace 
                ? null 
                : classSymbol.ContainingNamespace.ToDisplayString(),
            Properties: properties,
            IncludeType: includeType,
            Format: format);
    }
    
    /// <summary>
    /// 生成 GetInfo 方法的源代码
    /// </summary>
    private static string GenerateInfoMethod(ClassInfo classInfo)
    {
        var sb = new StringBuilder();
        
        sb.AppendLine("// <auto-generated/>");
        sb.AppendLine("#nullable enable");
        sb.AppendLine();
        
        // 命名空间
        if (classInfo.Namespace is not null)
        {
            sb.AppendLine($"namespace {classInfo.Namespace}");
            sb.AppendLine("{");
        }
        
        // 类定义
        var indent = classInfo.Namespace is not null ? "    " : "";
        sb.AppendLine($"{indent}partial class {classInfo.ClassName}");
        sb.AppendLine($"{indent}{{");
        
        // GetInfo 方法
        sb.AppendLine($"{indent}    /// <summary>");
        sb.AppendLine($"{indent}    /// 获取对象信息的字符串表示");
        sb.AppendLine($"{indent}    /// </summary>");
        sb.AppendLine($"{indent}    public string GetInfo()");
        sb.AppendLine($"{indent}    {{");
        
        // 生成方法体
        if (!string.IsNullOrEmpty(classInfo.Format))
        {
            // 使用自定义格式
            sb.AppendLine($"{indent}        return $\"{classInfo.Format}\";");
        }
        else
        {
            // 使用默认格式
            var typePrefix = classInfo.IncludeType ? $"{classInfo.ClassName} {{ " : "{ ";
            sb.Append($"{indent}        return $\"{typePrefix}");
            
            for (int i = 0; i < classInfo.Properties.Length; i++)
            {
                var prop = classInfo.Properties[i];
                if (i > 0)
                    sb.Append(", ");
                sb.Append($"{prop.Name} = {{{prop.Name}}}");
            }
            
            sb.AppendLine(" }\";");
        }
        
        sb.AppendLine($"{indent}    }}");
        sb.AppendLine($"{indent}}}");
        
        if (classInfo.Namespace is not null)
        {
            sb.AppendLine("}");
        }
        
        return sb.ToString();
    }
    
    /// <summary>
    /// 类信息记录
    /// </summary>
    private record ClassInfo(
        string ClassName,
        string? Namespace,
        ImmutableArray<PropertyInfo> Properties,
        bool IncludeType,
        string? Format);
    
    /// <summary>
    /// 属性信息记录
    /// </summary>
    private record PropertyInfo(string Name, string Type);
}

使用者项目配置

DotNet10Embedded.Consumer.csproj:

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <LangVersion>12</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <!-- 引用生成器项目 -->
    <ProjectReference Include="..\DotNet10Embedded.Generator\DotNet10Embedded.Generator.csproj"
                      OutputItemType="Analyzer"
                      ReferenceOutputAssembly="false" />
  </ItemGroup>

  <!-- 可选:启用生成的文件可见性(用于调试) -->
  <PropertyGroup>
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
    <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
  </PropertyGroup>

</Project>

使用示例代码

Person.cs:

csharp
using DotNet10Embedded.Generator;

namespace DotNet10Embedded.Consumer;

/// <summary>
/// 使用默认配置的示例
/// </summary>
[GenerateInfo]
public partial class Person
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
    public string Email { get; set; } = string.Empty;
}

Product.cs:

csharp
using DotNet10Embedded.Generator;

namespace DotNet10Embedded.Consumer;

/// <summary>
/// 使用自定义配置的示例
/// </summary>
[GenerateInfo(IncludeType = false, Format = "Product: {Name} (${Price})")]
public partial class Product
{
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public int Stock { get; set; }
}

Program.cs:

csharp
using DotNet10Embedded.Consumer;

// 测试 Person 类
var person = new Person
{
    Name = "Alice",
    Age = 30,
    Email = "alice@example.com"
};

Console.WriteLine(person.GetInfo());
// 输出: Person { Name = Alice, Age = 30, Email = alice@example.com }

// 测试 Product 类
var product = new Product
{
    Name = "Laptop",
    Price = 999.99m,
    Stock = 50
};

Console.WriteLine(product.GetInfo());
// 输出: Product: Laptop ($999.99)

// 测试 InternalsVisibleTo 场景
// 即使使用了 [assembly: InternalsVisibleTo("Tests")]
// 也不会产生 CS0436 警告,因为特性被标记为 [Embedded]

迁移指南

从传统方式迁移到 [Embedded]

迁移前(传统方式)

csharp
[Generator]
public class OldGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 传统方式:直接生成 internal 特性
        context.RegisterPostInitializationOutput(ctx =>
        {
            ctx.AddSource("GenerateInfoAttribute.g.cs", @"
namespace MyGenerator
{
    // ⚠️ 问题:使用 InternalsVisibleTo 时会产生 CS0436
    internal sealed class GenerateInfoAttribute : System.Attribute
    {
    }
}");
        });
        
        // ... 其余代码
    }
}

问题

  • ❌ 使用 [InternalsVisibleTo] 时产生 CS0436 警告
  • ❌ 类型在多个程序集中可见导致冲突
  • ❌ 需要额外配置来抑制警告

迁移后(使用 [Embedded])

csharp
[Generator]
public class NewGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // .NET 10 方式:使用 [Embedded] 特性
        context.RegisterPostInitializationOutput(ctx =>
        {
            // ✅ 步骤 1: 添加 EmbeddedAttribute 定义
            ctx.AddEmbeddedAttributeDefinition();
            
            // ✅ 步骤 2: 在特性上应用 [Embedded]
            ctx.AddSource("GenerateInfoAttribute.g.cs", @"
namespace MyGenerator
{
    [global::System.Runtime.CompilerServices.Embedded]
    internal sealed class GenerateInfoAttribute : System.Attribute
    {
    }
}");
        });
        
        // ... 其余代码保持不变
    }
}

优势

  • ✅ 完全解决 CS0436 警告
  • ✅ 类型自动隔离,不会冲突
  • ✅ 只需添加两行代码
  • ✅ 不需要额外配置

迁移步骤

步骤 1: 更新项目文件

xml
<!-- 确保使用 .NET 10 SDK -->
<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <LangVersion>12</LangVersion>
</PropertyGroup>

<!-- 更新 Roslyn 包版本 -->
<ItemGroup>
  <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
</ItemGroup>

步骤 2: 修改生成器代码

csharp
// 在 RegisterPostInitializationOutput 中添加
ctx.AddEmbeddedAttributeDefinition();

步骤 3: 在生成的特性上添加 [Embedded]

csharp
[global::System.Runtime.CompilerServices.Embedded]
internal sealed class YourAttribute : System.Attribute
{
    // ...
}

步骤 4: 测试验证

bash
# 清理并重新构建
dotnet clean
dotnet build

# 验证没有 CS0436 警告
# 验证生成的代码正确

从共享 DLL 方案迁移

如果你之前使用共享 DLL 方案来避免 CS0436:

迁移前(共享 DLL):

Solution/
├── MyGenerator/              # 生成器项目
├── MyGenerator.Attributes/   # 共享特性项目
└── MyConsumer/              # 使用者项目

迁移后([Embedded]):

Solution/
├── MyGenerator/              # 生成器项目(包含特性定义)
└── MyConsumer/              # 使用者项目

优势

  • ✅ 减少一个项目
  • ✅ 简化项目引用
  • ✅ 减少维护成本
  • ✅ 更清晰的项目结构

工作原理详解

EmbeddedAttribute 的定义

AddEmbeddedAttributeDefinition() 会生成以下代码:

csharp
// <auto-generated/>
namespace System.Runtime.CompilerServices
{
    /// <summary>
    /// 标记类型为嵌入式类型,不会通过 InternalsVisibleTo 暴露
    /// </summary>
    [global::System.AttributeUsage(
        global::System.AttributeTargets.Class | 
        global::System.AttributeTargets.Struct | 
        global::System.AttributeTargets.Enum | 
        global::System.AttributeTargets.Interface | 
        global::System.AttributeTargets.Delegate,
        Inherited = false,
        AllowMultiple = false)]
    internal sealed class EmbeddedAttribute : global::System.Attribute
    {
    }
}

编译器如何处理 [Embedded]

  1. 识别阶段

    • 编译器扫描所有类型
    • 识别标记了 [Embedded] 的类型
  2. 隔离阶段

    • 将嵌入式类型标记为"程序集私有"
    • 即使类型是 internal,也不会通过 [InternalsVisibleTo] 暴露
  3. 元数据生成

    • 在程序集元数据中添加特殊标记
    • 其他程序集无法看到这些类型
  4. 类型解析

    • 每个程序集都有自己的嵌入式类型副本
    • 不同程序集的同名嵌入式类型被视为不同类型

类型隔离示例

csharp
// Assembly A
[Embedded]
internal class MyAttribute : Attribute { }

// Assembly B
[Embedded]
internal class MyAttribute : Attribute { }

// 编译器视角:
// Assembly A 的 MyAttribute 和 Assembly B 的 MyAttribute 是不同的类型
// 即使它们有相同的名称和定义

与 InternalsVisibleTo 的交互

csharp
// Assembly A
[assembly: InternalsVisibleTo("Assembly B")]

[Embedded]
internal class MyAttribute : Attribute { }  // ✅ 不会暴露给 Assembly B

internal class OtherClass { }               // ✅ 会暴露给 Assembly B

最佳实践 vs 反模式

✅ 最佳实践

1. 使用 AddEmbeddedAttributeDefinition() API

csharp
// ✅ 推荐:使用 API
context.RegisterPostInitializationOutput(ctx =>
{
    ctx.AddEmbeddedAttributeDefinition();
    // ...
});
csharp
// ❌ 不推荐:手动生成定义
context.RegisterPostInitializationOutput(ctx =>
{
    ctx.AddSource("EmbeddedAttribute.g.cs", @"
namespace System.Runtime.CompilerServices
{
    internal sealed class EmbeddedAttribute : Attribute { }
}");
});

原因

  • API 确保定义完全正确
  • API 处理所有边界情况
  • API 更简洁易读

2. 总是使用完全限定名称

csharp
// ✅ 推荐:使用完全限定名称
[global::System.Runtime.CompilerServices.Embedded]
internal sealed class MyAttribute : global::System.Attribute
{
}
csharp
// ❌ 不推荐:使用简短名称
[Embedded]
internal sealed class MyAttribute : Attribute
{
}

原因

  • 避免命名空间冲突
  • 生成的代码更健壮
  • 不依赖 using 语句

3. 只在标记特性上使用 [Embedded]

csharp
// ✅ 推荐:只在标记特性上使用
[Embedded]
internal sealed class GenerateInfoAttribute : Attribute { }

// 生成的代码不需要 [Embedded]
partial class Person
{
    public string GetInfo() => "...";
}
csharp
// ❌ 不推荐:在生成的代码上使用
[Embedded]
partial class Person  // 不需要
{
    public string GetInfo() => "...";
}

原因

  • [Embedded] 只用于避免特性冲突
  • 生成的代码不需要隔离
  • 保持生成的代码简洁

4. 在特性中提供配置选项

csharp
// ✅ 推荐:提供配置选项
[Embedded]
internal sealed class GenerateInfoAttribute : Attribute
{
    public bool IncludeType { get; set; } = true;
    public string? Format { get; set; }
}
csharp
// ❌ 不推荐:没有配置选项
[Embedded]
internal sealed class GenerateInfoAttribute : Attribute
{
    // 没有属性,不够灵活
}

原因

  • 提供更多灵活性
  • 用户可以自定义行为
  • 更好的用户体验

❌ 常见反模式

1. 在不需要的地方使用 [Embedded]

csharp
// ❌ 反模式:在公共类型上使用
[Embedded]
public class MyPublicClass  // public 类型不需要 [Embedded]
{
}

问题

  • [Embedded] 只对 internal 类型有意义
  • 公共类型本来就是可见的
  • 浪费编译器资源

2. 忘记调用 AddEmbeddedAttributeDefinition()

csharp
// ❌ 反模式:使用 [Embedded] 但没有定义
context.RegisterPostInitializationOutput(ctx =>
{
    // 忘记调用 AddEmbeddedAttributeDefinition()
    
    ctx.AddSource("MyAttribute.g.cs", @"
[Embedded]  // ❌ 编译错误:找不到 EmbeddedAttribute
internal sealed class MyAttribute : Attribute { }
");
});

解决方案

csharp
// ✅ 正确做法
context.RegisterPostInitializationOutput(ctx =>
{
    ctx.AddEmbeddedAttributeDefinition();  // 必须先调用
    
    ctx.AddSource("MyAttribute.g.cs", @"
[Embedded]
internal sealed class MyAttribute : Attribute { }
");
});

3. 在 .NET 9 或更早版本使用 API

csharp
// ❌ 反模式:在旧版本使用新 API
<TargetFramework>net8.0</TargetFramework>

// 代码中使用
ctx.AddEmbeddedAttributeDefinition();  // ❌ API 不存在

解决方案

csharp
// ✅ 检查版本或使用条件编译
#if NET10_0_OR_GREATER
    ctx.AddEmbeddedAttributeDefinition();
#else
    // 使用其他方案
#endif

真实使用场景

场景 1: 大型企业应用

背景

  • 多个项目共享生成器
  • 使用 [InternalsVisibleTo] 进行单元测试
  • 需要避免类型冲突

解决方案

csharp
// 生成器项目
[Generator]
public class EnterpriseGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterPostInitializationOutput(ctx =>
        {
            // 使用 [Embedded] 避免冲突
            ctx.AddEmbeddedAttributeDefinition();
            
            ctx.AddSource("GenerateRepositoryAttribute.g.cs", @"
[global::System.Runtime.CompilerServices.Embedded]
internal sealed class GenerateRepositoryAttribute : System.Attribute
{
    public string ConnectionString { get; set; }
}");
        });
        
        // ... 生成仓储代码
    }
}

// 使用者项目 A
[assembly: InternalsVisibleTo("ProjectA.Tests")]

[GenerateRepository(ConnectionString = "...")]
public partial class UserRepository { }

// 使用者项目 B
[assembly: InternalsVisibleTo("ProjectB.Tests")]

[GenerateRepository(ConnectionString = "...")]
public partial class ProductRepository { }

// ✅ 两个项目都不会产生 CS0436 警告

场景 2: NuGet 包发布

背景

  • 将生成器打包为 NuGet 包
  • 用户可能在多个项目中使用
  • 需要确保兼容性

解决方案

xml
<!-- Generator.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <PackageId>MyCompany.SourceGenerator</PackageId>
    <Version>2.0.0</Version>
    <Description>使用 .NET 10 [Embedded] 特性的源生成器</Description>
  </PropertyGroup>
  
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
  </ItemGroup>
  
  <!-- 打包配置 -->
  <ItemGroup>
    <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
  </ItemGroup>
</Project>
csharp
// 生成器实现
[Generator]
public class PublicGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterPostInitializationOutput(ctx =>
        {
            // 使用 [Embedded] 确保用户不会遇到冲突
            ctx.AddEmbeddedAttributeDefinition();
            
            ctx.AddSource("GenerateAttribute.g.cs", @"
[global::System.Runtime.CompilerServices.Embedded]
internal sealed class GenerateAttribute : System.Attribute { }
");
        });
    }
}

场景 3: 微服务架构

背景

  • 多个微服务使用相同的生成器
  • 每个服务有自己的测试项目
  • 需要独立部署

解决方案

Microservices/
├── Common.Generator/           # 共享生成器
├── UserService/
│   ├── UserService.csproj
│   └── UserService.Tests/
├── OrderService/
│   ├── OrderService.csproj
│   └── OrderService.Tests/
└── ProductService/
    ├── ProductService.csproj
    └── ProductService.Tests/
csharp
// Common.Generator
[Generator]
public class DtoGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterPostInitializationOutput(ctx =>
        {
            ctx.AddEmbeddedAttributeDefinition();
            
            ctx.AddSource("GenerateDtoAttribute.g.cs", @"
[global::System.Runtime.CompilerServices.Embedded]
internal sealed class GenerateDtoAttribute : System.Attribute { }
");
        });
    }
}

// 每个服务都可以独立使用,不会冲突

性能影响分析

编译时性能

csharp
// 性能测试代码
[Fact]
public void Measure_Embedded_Performance()
{
    var source = GenerateLargeSource(1000);
    
    // 测试使用 [Embedded]
    var sw1 = Stopwatch.StartNew();
    var result1 = RunGeneratorWithEmbedded(source);
    sw1.Stop();
    
    // 测试不使用 [Embedded]
    var sw2 = Stopwatch.StartNew();
    var result2 = RunGeneratorWithoutEmbedded(source);
    sw2.Stop();
    
    // 性能差异通常 < 5%
    Console.WriteLine($"With [Embedded]: {sw1.ElapsedMilliseconds}ms");
    Console.WriteLine($"Without [Embedded]: {sw2.ElapsedMilliseconds}ms");
}

结果

  • ✅ [Embedded] 的性能开销极小(< 5%)
  • ✅ 编译器优化处理嵌入式类型
  • ✅ 对大型项目影响可忽略

运行时性能

csharp
// [Embedded] 只在编译时起作用
// 运行时没有任何性能影响

[Embedded]
internal class MyAttribute : Attribute { }

// 运行时,这个特性和普通特性完全一样
// 没有额外的检查或开销

结论

  • ✅ 运行时零开销
  • ✅ 生成的代码性能相同
  • ✅ 程序集大小几乎相同

内存使用

csharp
// 每个程序集都有自己的嵌入式类型副本
// 但由于特性类型很小,内存影响可忽略

// 假设特性大小:~200 字节
// 10 个程序集:~2 KB
// 影响:可忽略

常见问题(扩展)

Q1: AddEmbeddedAttributeDefinition() 生成的代码是什么?

A: 它生成 System.Runtime.CompilerServices.EmbeddedAttribute 的完整定义:

csharp
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(
        AttributeTargets.Class | AttributeTargets.Struct | 
        AttributeTargets.Enum | AttributeTargets.Interface | 
        AttributeTargets.Delegate,
        Inherited = false)]
    internal sealed class EmbeddedAttribute : Attribute
    {
    }
}

你可以通过启用 EmitCompilerGeneratedFiles 查看生成的文件。

Q2: 可以在同一个程序集中多次调用 AddEmbeddedAttributeDefinition() 吗?

A: 可以,但不推荐。编译器会自动处理重复定义。

csharp
// ✅ 可以工作,但不推荐
context.RegisterPostInitializationOutput(ctx =>
{
    ctx.AddEmbeddedAttributeDefinition();
    ctx.AddEmbeddedAttributeDefinition();  // 重复调用
});

// ✅ 推荐:只调用一次
context.RegisterPostInitializationOutput(ctx =>
{
    ctx.AddEmbeddedAttributeDefinition();
});

Q3: [Embedded] 特性可以应用在哪些类型上?

A: 根据定义,可以应用在:

  • class
  • struct
  • enum
  • interface
  • delegate
csharp
// ✅ 支持
[Embedded] internal class MyClass { }
[Embedded] internal struct MyStruct { }
[Embedded] internal enum MyEnum { }
[Embedded] internal interface IMyInterface { }
[Embedded] internal delegate void MyDelegate();

// ❌ 不支持
[Embedded] internal void MyMethod() { }  // 方法
[Embedded] internal int MyField;         // 字段

Q4: 如何在 .NET 9 中使用类似功能?

A: 在 .NET 9 中,你需要手动生成 EmbeddedAttribute 定义:

csharp
// .NET 9 兼容方式
context.RegisterPostInitializationOutput(ctx =>
{
    // 手动生成 EmbeddedAttribute
    ctx.AddSource("EmbeddedAttribute.g.cs", @"
namespace System.Runtime.CompilerServices
{
    [global::System.AttributeUsage(
        global::System.AttributeTargets.Class | 
        global::System.AttributeTargets.Struct | 
        global::System.AttributeTargets.Enum | 
        global::System.AttributeTargets.Interface | 
        global::System.AttributeTargets.Delegate,
        Inherited = false)]
    internal sealed class EmbeddedAttribute : global::System.Attribute
    {
    }
}");
    
    // 然后使用它
    ctx.AddSource("MyAttribute.g.cs", @"
[global::System.Runtime.CompilerServices.Embedded]
internal sealed class MyAttribute : System.Attribute { }
");
});

Q5: [Embedded] 特性会影响反射吗?

A: 不会。在运行时,嵌入式类型和普通类型完全一样。

csharp
[Embedded]
internal class MyAttribute : Attribute { }

// 运行时反射正常工作
var attributes = typeof(MyClass).GetCustomAttributes<MyAttribute>();
// ✅ 可以正常获取特性

Q6: 可以在 public 类型上使用 [Embedded] 吗?

A: 技术上可以,但没有意义。

csharp
// ❌ 没有意义
[Embedded]
public class MyPublicClass { }
// public 类型本来就是可见的,[Embedded] 不会改变任何行为

[Embedded] 只对 internal 类型有实际作用。

Q7: 如何验证 [Embedded] 是否生效?

A: 几种验证方法:

方法 1: 检查编译警告

csharp
// 添加 InternalsVisibleTo
[assembly: InternalsVisibleTo("TestProject")]

// 如果没有 CS0436 警告,说明 [Embedded] 生效了

方法 2: 使用 ILSpy 查看元数据

bash
# 使用 ILSpy 或 ildasm 查看程序集
# 嵌入式类型会有特殊的元数据标记

方法 3: 编写测试

csharp
[Fact]
public void Verify_Embedded_Attribute_Works()
{
    var source = @"
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""TestAssembly"")]

[MyAttribute]
public partial class TestClass { }
";

    var compilation = CreateCompilation(source);
    var generator = new MyGenerator();
    
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    driver = driver.RunGeneratorsAndUpdateCompilation(
        compilation,
        out var outputCompilation,
        out _);
    
    // 验证没有 CS0436 警告
    var cs0436 = outputCompilation.GetDiagnostics()
        .Where(d => d.Id == "CS0436");
    
    Assert.Empty(cs0436);
}

Q8: [Embedded] 和 [CompilerGenerated] 有什么区别?

A: 完全不同的用途:

特性[Embedded][CompilerGenerated]
用途隔离类型,避免冲突标记编译器生成的代码
可见性影响类型可见性不影响可见性
InternalsVisibleTo阻止暴露不影响
应用场景源生成器特性匿名类型、迭代器等
csharp
// [Embedded] - 用于隔离类型
[Embedded]
internal class MyAttribute : Attribute { }

// [CompilerGenerated] - 标记编译器生成的代码
[CompilerGenerated]
private class <>c__DisplayClass0_0  // 编译器生成的闭包类
{
}

Q9: 如何处理多个生成器都需要 EmbeddedAttribute 的情况?

A: 每个生成器都可以调用 AddEmbeddedAttributeDefinition(),编译器会自动处理重复。

csharp
// Generator 1
[Generator]
public class Generator1 : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterPostInitializationOutput(ctx =>
        {
            ctx.AddEmbeddedAttributeDefinition();  // 第一次调用
        });
    }
}

// Generator 2
[Generator]
public class Generator2 : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterPostInitializationOutput(ctx =>
        {
            ctx.AddEmbeddedAttributeDefinition();  // 第二次调用
        });
    }
}

// ✅ 编译器会自动合并,不会产生冲突

Q10: [Embedded] 特性可以继承吗?

A: 不可以。EmbeddedAttribute 的定义中 Inherited = false

csharp
[Embedded]
internal class BaseAttribute : Attribute { }

// ❌ 派生类不会自动获得 [Embedded]
internal class DerivedAttribute : BaseAttribute { }

// ✅ 需要显式应用
[Embedded]
internal class DerivedAttribute : BaseAttribute { }

Q11: 如何在多目标框架项目中使用?

A: 使用条件编译:

xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net8.0;net10.0</TargetFrameworks>
  </PropertyGroup>
</Project>
csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    context.RegisterPostInitializationOutput(ctx =>
    {
#if NET10_0_OR_GREATER
        // .NET 10+: 使用新 API
        ctx.AddEmbeddedAttributeDefinition();
#else
        // .NET 8/9: 手动生成
        ctx.AddSource("EmbeddedAttribute.g.cs", EmbeddedAttributeSource);
#endif
        
        ctx.AddSource("MyAttribute.g.cs", @"
[global::System.Runtime.CompilerServices.Embedded]
internal sealed class MyAttribute : System.Attribute { }
");
    });
}

Q12: [Embedded] 会影响序列化吗?

A: 不会。嵌入式类型在运行时和普通类型完全一样。

csharp
[Embedded]
internal class MyAttribute : Attribute
{
    public string Value { get; set; }
}

// ✅ 序列化正常工作
var attr = new MyAttribute { Value = "test" };
var json = JsonSerializer.Serialize(attr);
var deserialized = JsonSerializer.Deserialize<MyAttribute>(json);

Q13: 如何调试 [Embedded] 相关问题?

A: 几个调试技巧:

1. 启用生成文件输出

xml
<PropertyGroup>
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

2. 查看生成的文件

bash
# 查看生成的文件
dir obj\Debug\net10.0\GeneratedFiles

3. 使用 ILSpy 查看元数据

bash
# 使用 ILSpy 打开编译后的 DLL
# 查看 EmbeddedAttribute 和标记的类型

4. 检查诊断信息

csharp
var diagnostics = outputCompilation.GetDiagnostics();
foreach (var diag in diagnostics)
{
    Console.WriteLine($"{diag.Id}: {diag.GetMessage()}");
}

Q14: [Embedded] 和 NuGet 包版本冲突怎么办?

A: [Embedded] 特性可以避免这个问题。

csharp
// 场景:两个 NuGet 包都使用相同的特性名称

// Package A
[Embedded]
internal class GenerateAttribute : Attribute { }

// Package B
[Embedded]
internal class GenerateAttribute : Attribute { }

// ✅ 不会冲突,因为它们是嵌入式类型
// 每个包都有自己的副本

Q15: 如何测试使用 [Embedded] 的生成器?

A: 使用标准的生成器测试方法:

csharp
[Fact]
public void Test_Embedded_Generator()
{
    var source = @"
[GenerateInfo]
public partial class Person
{
    public string Name { get; set; }
}";

    var compilation = CreateCompilation(source);
    var generator = new InfoGenerator();
    
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    driver = driver.RunGeneratorsAndUpdateCompilation(
        compilation,
        out var outputCompilation,
        out _);
    
    // 验证生成成功
    Assert.False(HasErrors(outputCompilation));
    
    // 验证生成了 EmbeddedAttribute
    var embeddedAttr = outputCompilation.GetTypeByMetadataName(
        "System.Runtime.CompilerServices.EmbeddedAttribute");
    Assert.NotNull(embeddedAttr);
    
    // 验证生成了标记特性
    var generateInfoAttr = outputCompilation.GetTypeByMetadataName(
        "DotNet10Embedded.Generator.GenerateInfoAttribute");
    Assert.NotNull(generateInfoAttr);
    
    // 验证特性被标记为 [Embedded]
    var attrs = generateInfoAttr.GetAttributes();
    Assert.Contains(attrs, a => 
        a.AttributeClass?.Name == "EmbeddedAttribute");
}

扩展练习

练习 1: 基础使用

创建一个使用 [Embedded] 特性的简单生成器:

任务

  1. 创建生成器项目
  2. 使用 AddEmbeddedAttributeDefinition()
  3. 生成一个标记特性
  4. 在使用者项目中使用

验证

  • 生成器正常工作
  • 没有 CS0436 警告
  • 生成的代码正确

练习 2: 迁移现有生成器

将一个现有的生成器迁移到使用 [Embedded]

任务

  1. 找到一个现有的生成器项目
  2. 添加 AddEmbeddedAttributeDefinition() 调用
  3. 在特性上添加 [Embedded]
  4. 测试迁移结果

验证

  • 功能保持不变
  • CS0436 警告消失
  • 测试全部通过

练习 3: 多框架支持

创建一个支持多个框架的生成器:

任务

  1. 配置多目标框架(net8.0, net10.0)
  2. 使用条件编译处理不同版本
  3. 在 .NET 10 使用 API,在 .NET 8 手动生成
  4. 测试两个版本

验证

  • 两个版本都能编译
  • 两个版本功能一致
  • 适当的条件编译

练习 4: 高级配置

创建一个带有高级配置选项的生成器:

任务

  1. 在特性中添加多个配置属性
  2. 在生成器中读取配置
  3. 根据配置生成不同的代码
  4. 编写测试验证各种配置

验证

  • 所有配置选项工作正常
  • 生成的代码符合配置
  • 测试覆盖所有配置组合

下一步

完成 .NET 10 嵌入式生成器的学习后,你可以:

  1. 深入 API 参考 - 增量管道 API
  2. 学习最佳实践 - 最佳实践指南
  3. 探索高级模式 - 高级模式
  4. 了解性能优化 - 增量生成器示例
  5. 查看更多示例 - 示例索引

相关文档

基于 MIT 许可发布