.NET 10 [Embedded] 特性
学习 .NET 10 中引入的新特性:AddEmbeddedAttributeDefinition() API 和 [Embedded] 特性。
问题背景
CS0436 警告的产生
在 .NET 10 之前,源生成器通常使用 RegisterPostInitializationOutput 来生成标记特性:
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddSource("MyAttribute.g.cs", @"
namespace MyGenerator
{
internal sealed class MyAttribute : System.Attribute { }
}");
});2
3
4
5
6
7
8
当项目使用 [InternalsVisibleTo] 时会出现问题:
[assembly: InternalsVisibleTo("MyProject.Tests")]此时编译器会报告 CS0436 警告:
warning CS0436: The type 'MyAttribute' conflicts with the imported type 'MyAttribute'.NET 10 解决方案
什么是 [Embedded] 特性?
[Embedded] 是一个编译器识别的特殊特性,用于标记"嵌入式"类型。当一个类型被标记为 [Embedded] 时:
- 不会在程序集外可见: 即使类型是
internal,也不会通过[InternalsVisibleTo]暴露 - 避免类型冲突: 每个程序集都有自己的嵌入式类型副本,互不干扰
- 编译器优化: 编译器会特殊处理这些类型
AddEmbeddedAttributeDefinition() API
.NET 10 引入了新的 API 来简化使用:
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 { }
}");
});2
3
4
5
6
7
8
9
10
11
12
13
实现示例
[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));
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[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 版本
dotnet --version向后兼容性
AddEmbeddedAttributeDefinition() API 仅在 .NET 10+ 中可用。如果需要支持更早的版本:
- 使用共享 DLL 方案(适用于 .NET 5+)
- 手动生成 [Embedded] 特性(适用于 .NET 7+)
- 忽略 CS0436 警告(不推荐)
[Embedded] 特性的严格要求
[Embedded] 特性必须完全匹配以下定义:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Struct |
AttributeTargets.Enum | AttributeTargets.Interface |
AttributeTargets.Delegate,
Inherited = false)]
internal sealed class EmbeddedAttribute : Attribute
{
}
}2
3
4
5
6
7
8
9
10
11
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.cs2
3
4
5
6
7
8
9
10
11
12
13
14
详细代码实现
生成器项目配置
DotNet10Embedded.Generator.csproj:
<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>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
完整生成器实现
InfoGenerator.cs:
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);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
使用者项目配置
DotNet10Embedded.Consumer.csproj:
<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>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
使用示例代码
Person.cs:
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;
}2
3
4
5
6
7
8
9
10
11
12
13
14
Product.cs:
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; }
}2
3
4
5
6
7
8
9
10
11
12
13
14
Program.cs:
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]2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
迁移指南
从传统方式迁移到 [Embedded]
迁移前(传统方式)
[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
{
}
}");
});
// ... 其余代码
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
问题:
- ❌ 使用
[InternalsVisibleTo]时产生 CS0436 警告 - ❌ 类型在多个程序集中可见导致冲突
- ❌ 需要额外配置来抑制警告
迁移后(使用 [Embedded])
[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
{
}
}");
});
// ... 其余代码保持不变
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
优势:
- ✅ 完全解决 CS0436 警告
- ✅ 类型自动隔离,不会冲突
- ✅ 只需添加两行代码
- ✅ 不需要额外配置
迁移步骤
步骤 1: 更新项目文件
<!-- 确保使用 .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
3
4
5
6
7
8
9
10
步骤 2: 修改生成器代码
// 在 RegisterPostInitializationOutput 中添加
ctx.AddEmbeddedAttributeDefinition();2
步骤 3: 在生成的特性上添加 [Embedded]
[global::System.Runtime.CompilerServices.Embedded]
internal sealed class YourAttribute : System.Attribute
{
// ...
}2
3
4
5
步骤 4: 测试验证
# 清理并重新构建
dotnet clean
dotnet build
# 验证没有 CS0436 警告
# 验证生成的代码正确2
3
4
5
6
从共享 DLL 方案迁移
如果你之前使用共享 DLL 方案来避免 CS0436:
迁移前(共享 DLL):
Solution/
├── MyGenerator/ # 生成器项目
├── MyGenerator.Attributes/ # 共享特性项目
└── MyConsumer/ # 使用者项目2
3
4
迁移后([Embedded]):
Solution/
├── MyGenerator/ # 生成器项目(包含特性定义)
└── MyConsumer/ # 使用者项目2
3
优势:
- ✅ 减少一个项目
- ✅ 简化项目引用
- ✅ 减少维护成本
- ✅ 更清晰的项目结构
工作原理详解
EmbeddedAttribute 的定义
AddEmbeddedAttributeDefinition() 会生成以下代码:
// <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
{
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
编译器如何处理 [Embedded]
识别阶段:
- 编译器扫描所有类型
- 识别标记了
[Embedded]的类型
隔离阶段:
- 将嵌入式类型标记为"程序集私有"
- 即使类型是
internal,也不会通过[InternalsVisibleTo]暴露
元数据生成:
- 在程序集元数据中添加特殊标记
- 其他程序集无法看到这些类型
类型解析:
- 每个程序集都有自己的嵌入式类型副本
- 不同程序集的同名嵌入式类型被视为不同类型
类型隔离示例
// Assembly A
[Embedded]
internal class MyAttribute : Attribute { }
// Assembly B
[Embedded]
internal class MyAttribute : Attribute { }
// 编译器视角:
// Assembly A 的 MyAttribute 和 Assembly B 的 MyAttribute 是不同的类型
// 即使它们有相同的名称和定义2
3
4
5
6
7
8
9
10
11
与 InternalsVisibleTo 的交互
// Assembly A
[assembly: InternalsVisibleTo("Assembly B")]
[Embedded]
internal class MyAttribute : Attribute { } // ✅ 不会暴露给 Assembly B
internal class OtherClass { } // ✅ 会暴露给 Assembly B2
3
4
5
6
7
最佳实践 vs 反模式
✅ 最佳实践
1. 使用 AddEmbeddedAttributeDefinition() API
// ✅ 推荐:使用 API
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddEmbeddedAttributeDefinition();
// ...
});2
3
4
5
6
// ❌ 不推荐:手动生成定义
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddSource("EmbeddedAttribute.g.cs", @"
namespace System.Runtime.CompilerServices
{
internal sealed class EmbeddedAttribute : Attribute { }
}");
});2
3
4
5
6
7
8
9
原因:
- API 确保定义完全正确
- API 处理所有边界情况
- API 更简洁易读
2. 总是使用完全限定名称
// ✅ 推荐:使用完全限定名称
[global::System.Runtime.CompilerServices.Embedded]
internal sealed class MyAttribute : global::System.Attribute
{
}2
3
4
5
// ❌ 不推荐:使用简短名称
[Embedded]
internal sealed class MyAttribute : Attribute
{
}2
3
4
5
原因:
- 避免命名空间冲突
- 生成的代码更健壮
- 不依赖 using 语句
3. 只在标记特性上使用 [Embedded]
// ✅ 推荐:只在标记特性上使用
[Embedded]
internal sealed class GenerateInfoAttribute : Attribute { }
// 生成的代码不需要 [Embedded]
partial class Person
{
public string GetInfo() => "...";
}2
3
4
5
6
7
8
9
// ❌ 不推荐:在生成的代码上使用
[Embedded]
partial class Person // 不需要
{
public string GetInfo() => "...";
}2
3
4
5
6
原因:
- [Embedded] 只用于避免特性冲突
- 生成的代码不需要隔离
- 保持生成的代码简洁
4. 在特性中提供配置选项
// ✅ 推荐:提供配置选项
[Embedded]
internal sealed class GenerateInfoAttribute : Attribute
{
public bool IncludeType { get; set; } = true;
public string? Format { get; set; }
}2
3
4
5
6
7
// ❌ 不推荐:没有配置选项
[Embedded]
internal sealed class GenerateInfoAttribute : Attribute
{
// 没有属性,不够灵活
}2
3
4
5
6
原因:
- 提供更多灵活性
- 用户可以自定义行为
- 更好的用户体验
❌ 常见反模式
1. 在不需要的地方使用 [Embedded]
// ❌ 反模式:在公共类型上使用
[Embedded]
public class MyPublicClass // public 类型不需要 [Embedded]
{
}2
3
4
5
问题:
- [Embedded] 只对 internal 类型有意义
- 公共类型本来就是可见的
- 浪费编译器资源
2. 忘记调用 AddEmbeddedAttributeDefinition()
// ❌ 反模式:使用 [Embedded] 但没有定义
context.RegisterPostInitializationOutput(ctx =>
{
// 忘记调用 AddEmbeddedAttributeDefinition()
ctx.AddSource("MyAttribute.g.cs", @"
[Embedded] // ❌ 编译错误:找不到 EmbeddedAttribute
internal sealed class MyAttribute : Attribute { }
");
});2
3
4
5
6
7
8
9
10
解决方案:
// ✅ 正确做法
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddEmbeddedAttributeDefinition(); // 必须先调用
ctx.AddSource("MyAttribute.g.cs", @"
[Embedded]
internal sealed class MyAttribute : Attribute { }
");
});2
3
4
5
6
7
8
9
10
3. 在 .NET 9 或更早版本使用 API
// ❌ 反模式:在旧版本使用新 API
<TargetFramework>net8.0</TargetFramework>
// 代码中使用
ctx.AddEmbeddedAttributeDefinition(); // ❌ API 不存在2
3
4
5
解决方案:
// ✅ 检查版本或使用条件编译
#if NET10_0_OR_GREATER
ctx.AddEmbeddedAttributeDefinition();
#else
// 使用其他方案
#endif2
3
4
5
6
真实使用场景
场景 1: 大型企业应用
背景:
- 多个项目共享生成器
- 使用
[InternalsVisibleTo]进行单元测试 - 需要避免类型冲突
解决方案:
// 生成器项目
[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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
场景 2: NuGet 包发布
背景:
- 将生成器打包为 NuGet 包
- 用户可能在多个项目中使用
- 需要确保兼容性
解决方案:
<!-- 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>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 生成器实现
[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 { }
");
});
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
场景 3: 微服务架构
背景:
- 多个微服务使用相同的生成器
- 每个服务有自己的测试项目
- 需要独立部署
解决方案:
Microservices/
├── Common.Generator/ # 共享生成器
├── UserService/
│ ├── UserService.csproj
│ └── UserService.Tests/
├── OrderService/
│ ├── OrderService.csproj
│ └── OrderService.Tests/
└── ProductService/
├── ProductService.csproj
└── ProductService.Tests/2
3
4
5
6
7
8
9
10
11
// 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 { }
");
});
}
}
// 每个服务都可以独立使用,不会冲突2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
性能影响分析
编译时性能
// 性能测试代码
[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");
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
结果:
- ✅ [Embedded] 的性能开销极小(< 5%)
- ✅ 编译器优化处理嵌入式类型
- ✅ 对大型项目影响可忽略
运行时性能
// [Embedded] 只在编译时起作用
// 运行时没有任何性能影响
[Embedded]
internal class MyAttribute : Attribute { }
// 运行时,这个特性和普通特性完全一样
// 没有额外的检查或开销2
3
4
5
6
7
8
结论:
- ✅ 运行时零开销
- ✅ 生成的代码性能相同
- ✅ 程序集大小几乎相同
内存使用
// 每个程序集都有自己的嵌入式类型副本
// 但由于特性类型很小,内存影响可忽略
// 假设特性大小:~200 字节
// 10 个程序集:~2 KB
// 影响:可忽略2
3
4
5
6
常见问题(扩展)
Q1: AddEmbeddedAttributeDefinition() 生成的代码是什么?
A: 它生成 System.Runtime.CompilerServices.EmbeddedAttribute 的完整定义:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Struct |
AttributeTargets.Enum | AttributeTargets.Interface |
AttributeTargets.Delegate,
Inherited = false)]
internal sealed class EmbeddedAttribute : Attribute
{
}
}2
3
4
5
6
7
8
9
10
11
你可以通过启用 EmitCompilerGeneratedFiles 查看生成的文件。
Q2: 可以在同一个程序集中多次调用 AddEmbeddedAttributeDefinition() 吗?
A: 可以,但不推荐。编译器会自动处理重复定义。
// ✅ 可以工作,但不推荐
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddEmbeddedAttributeDefinition();
ctx.AddEmbeddedAttributeDefinition(); // 重复调用
});
// ✅ 推荐:只调用一次
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddEmbeddedAttributeDefinition();
});2
3
4
5
6
7
8
9
10
11
12
Q3: [Embedded] 特性可以应用在哪些类型上?
A: 根据定义,可以应用在:
classstructenuminterfacedelegate
// ✅ 支持
[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; // 字段2
3
4
5
6
7
8
9
10
Q4: 如何在 .NET 9 中使用类似功能?
A: 在 .NET 9 中,你需要手动生成 EmbeddedAttribute 定义:
// .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 { }
");
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Q5: [Embedded] 特性会影响反射吗?
A: 不会。在运行时,嵌入式类型和普通类型完全一样。
[Embedded]
internal class MyAttribute : Attribute { }
// 运行时反射正常工作
var attributes = typeof(MyClass).GetCustomAttributes<MyAttribute>();
// ✅ 可以正常获取特性2
3
4
5
6
Q6: 可以在 public 类型上使用 [Embedded] 吗?
A: 技术上可以,但没有意义。
// ❌ 没有意义
[Embedded]
public class MyPublicClass { }
// public 类型本来就是可见的,[Embedded] 不会改变任何行为2
3
4
[Embedded] 只对 internal 类型有实际作用。
Q7: 如何验证 [Embedded] 是否生效?
A: 几种验证方法:
方法 1: 检查编译警告
// 添加 InternalsVisibleTo
[assembly: InternalsVisibleTo("TestProject")]
// 如果没有 CS0436 警告,说明 [Embedded] 生效了2
3
4
方法 2: 使用 ILSpy 查看元数据
# 使用 ILSpy 或 ildasm 查看程序集
# 嵌入式类型会有特殊的元数据标记2
方法 3: 编写测试
[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);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Q8: [Embedded] 和 [CompilerGenerated] 有什么区别?
A: 完全不同的用途:
| 特性 | [Embedded] | [CompilerGenerated] |
|---|---|---|
| 用途 | 隔离类型,避免冲突 | 标记编译器生成的代码 |
| 可见性 | 影响类型可见性 | 不影响可见性 |
| InternalsVisibleTo | 阻止暴露 | 不影响 |
| 应用场景 | 源生成器特性 | 匿名类型、迭代器等 |
// [Embedded] - 用于隔离类型
[Embedded]
internal class MyAttribute : Attribute { }
// [CompilerGenerated] - 标记编译器生成的代码
[CompilerGenerated]
private class <>c__DisplayClass0_0 // 编译器生成的闭包类
{
}2
3
4
5
6
7
8
9
Q9: 如何处理多个生成器都需要 EmbeddedAttribute 的情况?
A: 每个生成器都可以调用 AddEmbeddedAttributeDefinition(),编译器会自动处理重复。
// 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(); // 第二次调用
});
}
}
// ✅ 编译器会自动合并,不会产生冲突2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Q10: [Embedded] 特性可以继承吗?
A: 不可以。EmbeddedAttribute 的定义中 Inherited = false。
[Embedded]
internal class BaseAttribute : Attribute { }
// ❌ 派生类不会自动获得 [Embedded]
internal class DerivedAttribute : BaseAttribute { }
// ✅ 需要显式应用
[Embedded]
internal class DerivedAttribute : BaseAttribute { }2
3
4
5
6
7
8
9
Q11: 如何在多目标框架项目中使用?
A: 使用条件编译:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
</PropertyGroup>
</Project>2
3
4
5
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 { }
");
});
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Q12: [Embedded] 会影响序列化吗?
A: 不会。嵌入式类型在运行时和普通类型完全一样。
[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);2
3
4
5
6
7
8
9
10
Q13: 如何调试 [Embedded] 相关问题?
A: 几个调试技巧:
1. 启用生成文件输出:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
</PropertyGroup>2
3
4
2. 查看生成的文件:
# 查看生成的文件
dir obj\Debug\net10.0\GeneratedFiles2
3. 使用 ILSpy 查看元数据:
# 使用 ILSpy 打开编译后的 DLL
# 查看 EmbeddedAttribute 和标记的类型2
4. 检查诊断信息:
var diagnostics = outputCompilation.GetDiagnostics();
foreach (var diag in diagnostics)
{
Console.WriteLine($"{diag.Id}: {diag.GetMessage()}");
}2
3
4
5
Q14: [Embedded] 和 NuGet 包版本冲突怎么办?
A: [Embedded] 特性可以避免这个问题。
// 场景:两个 NuGet 包都使用相同的特性名称
// Package A
[Embedded]
internal class GenerateAttribute : Attribute { }
// Package B
[Embedded]
internal class GenerateAttribute : Attribute { }
// ✅ 不会冲突,因为它们是嵌入式类型
// 每个包都有自己的副本2
3
4
5
6
7
8
9
10
11
12
Q15: 如何测试使用 [Embedded] 的生成器?
A: 使用标准的生成器测试方法:
[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");
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
扩展练习
练习 1: 基础使用
创建一个使用 [Embedded] 特性的简单生成器:
任务:
- 创建生成器项目
- 使用
AddEmbeddedAttributeDefinition() - 生成一个标记特性
- 在使用者项目中使用
验证:
- 生成器正常工作
- 没有 CS0436 警告
- 生成的代码正确
练习 2: 迁移现有生成器
将一个现有的生成器迁移到使用 [Embedded]:
任务:
- 找到一个现有的生成器项目
- 添加
AddEmbeddedAttributeDefinition()调用 - 在特性上添加
[Embedded] - 测试迁移结果
验证:
- 功能保持不变
- CS0436 警告消失
- 测试全部通过
练习 3: 多框架支持
创建一个支持多个框架的生成器:
任务:
- 配置多目标框架(net8.0, net10.0)
- 使用条件编译处理不同版本
- 在 .NET 10 使用 API,在 .NET 8 手动生成
- 测试两个版本
验证:
- 两个版本都能编译
- 两个版本功能一致
- 适当的条件编译
练习 4: 高级配置
创建一个带有高级配置选项的生成器:
任务:
- 在特性中添加多个配置属性
- 在生成器中读取配置
- 根据配置生成不同的代码
- 编写测试验证各种配置
验证:
- 所有配置选项工作正常
- 生成的代码符合配置
- 测试覆盖所有配置组合
下一步
完成 .NET 10 嵌入式生成器的学习后,你可以: