CompilerGenerated与GeneratedCode区别

前言

最近在捣鼓代码生成器,基于 Roslyn,我们可以让生成器项目生成我们的目标 C# 代码,这个也是MVVM Toolkit的实现方式,在查看生成代码的过程中,我们经常会遇到一些特殊的特性,如 GeneratedCodeAttribute ,刚好我还遇到过 CompilerGeneratedAttribute。感觉两个特性差不多,都可以用于标识代码的生成来源,帮助开发者和其他工具更好地理解和处理代码。

GeneratedCodeAttribute 解析

定义与用途

GeneratedCodeAttribute 是一个系统提供的特性,定义在 System.CodeDom.Compiler 命名空间,用于标记由工具或编译器生成的代码。它通常包含两个参数:生成工具的名称和版本号。

[AttributeUsage(AttributeTargets.All, Inherited = false)] public sealed class GeneratedCodeAttribute : Attribute {     public string Tool { get; }     public string Version { get; }      public GeneratedCodeAttribute(string tool, string version)     {         Tool = tool;         Version = version;     } } 

这个特性的主要用途是:

  • 标识生成的代码:当你使用 Source Generator、T4 模板、Roslyn API 或其他代码生成工具时,可以在生成的文件中添加这个特性,以明确指出代码是由哪个工具生成的。
  • 避免误修改:标记为 GeneratedCode 的代码可以提醒开发者不要直接编辑这些文件,因为它们是自动生成的,任何手动修改可能会在下次生成时丢失。
  • 分析器和工具支持:某些分析器(如 Roslyn 分析器)和工具会识别并特殊处理带有 GeneratedCodeAttribute 的代码,例如忽略代码覆盖率统计或特定的代码分析规则。

示例

假设你有一个 Source Generator 工具名为 MyCustomTool,版本为 1.0.0,你可以这样标记生成的代码:

[GeneratedCode("MyCustomTool", "1.0.0")] public partial class MyClass {     // 自动生成的代码 } 

CompilerGeneratedAttribute 解析

定义与用途

CompilerGeneratedAttribute 定义在 System.Runtime.CompilerServices 命名空间,是一个更具体的特性,用于标记由 C# 编译器自动生成的代码片段。它没有参数,仅表示代码是由编译器生成的。

[AttributeUsage(AttributeTargets.All, Inherited = false)] public sealed class CompilerGeneratedAttribute : Attribute { } 

这个特性的主要用途是:

  • 标识编译器生成的代码:当编译器为了实现某些语言特性(如匿名类型、迭代器、异步方法等)而自动生成代码时,会自动添加这个特性。这有助于区分用户编写的代码和编译器生成的代码。
  • 内部实现细节:这个特性主要用于内部实现细节,普通开发者通常不需要手动添加它。它是编译器用来标记其生成的代码的一种方式。

示例

编译器生成的代码片段可能如下所示:

[CompilerGenerated] private sealed class <>c__DisplayClass1_0 {     public int x;      internal void <Method>b__0()     {         Console.WriteLine(x);     } } 

区别与选择

虽然 GeneratedCodeAttributeCompilerGeneratedAttribute 都用于标识代码的生成来源,但它们有着不同的用途和适用场景。

  • 来源不同

    • GeneratedCodeAttribute 通常由外部工具或源代码生成器添加,以标识代码是由某个工具生成的,一般来说是出于编码人员的自身目标。
    • CompilerGeneratedAttribute 由 C# 编译器自身添加,用于标识编译器生成的代码片段。
  • 应用场景

    • 如果你正在开发 Source Generator 或其他代码生成工具,并希望标记生成的代码以便后续处理或提醒开发者不要直接编辑这些文件,应该手动标记使用 GeneratedCodeAttribute
    • 不应该手动添加该特性,如果你在查看编译后的代码,发现带有 CompilerGeneratedAttribute 的类或成员,这通常是编译器为了实现某些语言特性而生成的代码,不应被手动修改。

代码生成器应用示例

MVVM Toolkit 就按照这个标准开发,假设你正在开发一个 Source Generator 来生成部分类文件:

using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using System.CodeDom.Compiler;  public class MySourceGenerator : ISourceGenerator {     public void Initialize(GeneratorInitializationContext context)     {         // Initialization logic if needed     }      public void Execute(GeneratorExecutionContext context)     {         var sourceBuilder = new StringBuilder();         sourceBuilder.AppendLine("[GeneratedCode("MyCustomTool", "1.0.0")]");         sourceBuilder.AppendLine("public partial class MyClass");         sourceBuilder.AppendLine("{");         sourceBuilder.AppendLine("    public string MyProperty { get; set; }");         sourceBuilder.AppendLine("}");          context.AddSource("MyClass.g.cs", sourceBuilder.ToString());     } } 

上面代码中,GeneratedCodeAttribute 被用来标记生成的代码,确保其他工具和开发者知道这段代码是由 MyCustomTool 生成的。

一些建议:

  • 不适用于用户可修改的模板:如果有一个代码生成工具生成的模板,用户可能会根据需要对其进行修改,那么就不应该使用 GeneratedCodeAttribute 标记这些模板。因为一旦代码被手动修改,再用 GeneratedCodeAttribute 标记就不再准确了,而且可能会误导其他工具忽略这些手动修改的内容。
  • 部分类的特殊处理:当生成的代码是部分类的一部分时,不要在整个类上应用 GeneratedCodeAttribute。相反,你应该仅将此特性应用于该部分类中生成的具体成员(如方法、字段、属性等)。这是因为部分类可以有多个文件定义,而用户可能在其他文件中添加自己的实现。通过只标记生成的成员,你可以确保只有自动生成的部分被正确标识,而不会影响用户添加的代码。(这个可以看 MVVM Toolkit 生成的代码)

总结

简单说来:

  • GeneratedCodeAttribute 主要用于标记由工具或编译器生成的代码,特别是那些会频繁重新生成的代码。这有助于开发者和其他工具识别这些代码片段,并避免对它们进行不必要的修改。
  • CompilerGeneratedAttribute 一般不要手动添加到代码中。

参考文献

发表评论

相关文章