【光照】Unity[光照探针]的作用与工作原理

缺刺埠醒合集 - SourceGenerator(2)

.NET源码生成器

使用SyntaxTree生成代码及简化语法

03-09

.NET源码生成器

之SyntaxTree踩坑

03-10

收起

一、不可变性的坑

  1. 节点不可变

record调用AddParameterListParameters后record并不会修改

以下Case中record的代码依然是record Person;

var record = SyntaxGenerator.RecordDeclaration("Person")

.WithSemicolonToken();

record.AddParameterListParameters(SyntaxGenerator.StringType.Parameter("Name"));

// record Person;

  1. 可以用变量接收更改后的结果

record和record2是不一样的

record代码为record Person;

record2代码为record Person(string Name);

var record = SyntaxGenerator.RecordDeclaration("Person")

.WithSemicolonToken();

var record2 = record.AddParameterListParameters(SyntaxGenerator.StringType.Parameter("Name"));

  1. 不可变的集合

SyntaxTree常用的SyntaxList和SeparatedSyntaxList都是不可变的

以下集合初始化和Add都无效

需要特别注意

如果用变量赋值Add的返回值是可以的

var list = new SeparatedSyntaxList

{

SyntaxGenerator.IntType.Parameter("Id")

};

list.Add(SyntaxGenerator.StringType.Parameter("Name"));

Assert.Empty(list);

3.1 EasySyntax用List保存临时结果

List保存临时结果,最后组装SyntaxTree

public class SyntaxGenerator

{

protected readonly List _usings = usings;

protected readonly TypeDeclarationSyntax _type = type;

protected readonly List _parameters = [];

protected readonly List _members = members;

public virtual CompilationUnitSyntax Build()

=> Build(_usings, _type, [.. _parameters], [.. _members]

// ...

}

public abstract class StatementCollect

{

protected readonly List _statements = [];

public static StatementSyntax Block(List statements)

{

return statements.Count switch

{

0 => SyntaxFactory.EmptyStatement(),

1 => statements[0],

_ => SyntaxFactory.Block(statements),

};

}

// ...

}

二、"类型"系统的坑

这里说的"类型"是指类型名或者说类型名占位符

用于表示变量、属性和方法返回值类型

  1. 预定义类型

使用SyntaxFactory.PredefinedType定义预定义类型需要传个SyntaxToken

但是这个SyntaxToken不满足条件就会抛异常

指定16种情况,类似枚举

却没有一对应的类型、枚举或属性

public static PredefinedTypeSyntax PredefinedType(SyntaxToken keyword)

{

switch (keyword.Kind())

{

case SyntaxKind.BoolKeyword:

case SyntaxKind.ByteKeyword:

case SyntaxKind.SByteKeyword:

case SyntaxKind.IntKeyword:

case SyntaxKind.UIntKeyword:

case SyntaxKind.ShortKeyword:

case SyntaxKind.UShortKeyword:

case SyntaxKind.LongKeyword:

case SyntaxKind.ULongKeyword:

case SyntaxKind.FloatKeyword:

case SyntaxKind.DoubleKeyword:

case SyntaxKind.DecimalKeyword:

case SyntaxKind.StringKeyword:

case SyntaxKind.CharKeyword:

case SyntaxKind.ObjectKeyword:

case SyntaxKind.VoidKeyword: break;

default: throw new ArgumentException(nameof(keyword));

}

return (PredefinedTypeSyntax)Syntax.InternalSyntax.SyntaxFactory.PredefinedType((Syntax.InternalSyntax.SyntaxToken)keyword.Node!).CreateRed();

}

1.1 EasySyntax中填了这个坑

SyntaxGenerator的其中16个静态属性表示这16种预定义类型

///
/// bool

///

public static PredefinedTypeSyntax BoolType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword));

///
/// byte

///

public static PredefinedTypeSyntax ByteType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ByteKeyword));

///
/// sbyte

///

public static PredefinedTypeSyntax SByteType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.SByteKeyword));

///
/// int

///

public static PredefinedTypeSyntax IntType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword));

///
/// uint

///

public static PredefinedTypeSyntax UIntType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UIntKeyword));

///
/// short

///

public static PredefinedTypeSyntax ShortType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ShortKeyword));

///
/// ushort

///

public static PredefinedTypeSyntax UShortType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UShortKeyword));

///
/// long

///

public static PredefinedTypeSyntax LongType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.LongKeyword));

///
/// ulong

///

public static PredefinedTypeSyntax ULongType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ULongKeyword));

///
/// float

///

public static PredefinedTypeSyntax FloatType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.FloatKeyword));

///
/// double

///

public static PredefinedTypeSyntax DoubleType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword));

///
/// decimal

///

public static PredefinedTypeSyntax DecimalType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DecimalKeyword));

///
/// string

///

public static PredefinedTypeSyntax StringType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword));

///
/// char

///

public static PredefinedTypeSyntax CharType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.CharKeyword));

///
/// object

///

public static PredefinedTypeSyntax ObjectType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword));

///
/// void

///

public static PredefinedTypeSyntax VoidType => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword));

  1. 普通类型

一个简单的类型名,非预定义的16种情况

IdentifierNameSyntax是TypeSyntax子类,可以表示类型名

IdentifierNameSyntax是标识名,也可以表示变量名、字段名、属性名、方法名或命名空间

IdentifierNameSyntax是TypeSyntax子类这个事实笔者也是花了一些时间才消化的

这是SyntaxTree的设计哲学,设计如此

想通了也好理解,类名是个标识符、变量名和类型成员名何尝不是标识符,没必要区分开

SyntaxFactory.IdentifierName("User")可以表示User类

SyntaxFactory.IdentifierName("user")可以表示User类的实例

var type = SyntaxFactory.IdentifierName("User");

var user = SyntaxFactory.IdentifierName("user");

// User user = new()

var variable = type.Variable(user.Identifier, SyntaxFactory.ImplicitObjectCreationExpression());

  1. 限定名类型

就是含命名空间的类名

QualifiedNameSyntax也是TypeSyntax子类,可以表示限定名类型

同样QualifiedNameSyntax也可以表示成员名

Models.User可以表示特定的类

user1.Name自然也可以表示特定的属性

var type = SyntaxFactory.IdentifierName("User");

var user = SyntaxFactory.IdentifierName("user1");

var property = SyntaxFactory.IdentifierName("Name");

var @namespace = SyntaxFactory.IdentifierName("Models");

// Models.User

var qualifiedType = type.Qualified(@namespace);

// user1.Name

var qualifiedProperty = property.Qualified(user1);

  1. 泛型

就是含类型参数的类名

GenericNameSyntax也是TypeSyntax子类,可以表示泛型类型

GenericNameSyntax也可以表示泛型方法

// List

var listType = SyntaxGenerator.Generic("List", SyntaxGenerator.IntType);

// GetFieldValue

var method = SyntaxGenerator.Generic("GetFieldValue", SyntaxGenerator.IntType);

  1. 泛型限定名

就是含命名空间的泛型

泛型限定名用GenericNameSyntax还是QualifiedNameSyntax呢

5.1 ParseTypeName得到的是QualifiedNameSyntax

SyntaxFactory.ParseTypeName("System.Collections.Generic.List")的实际类型QualifiedNameSyntax

但是ParseTypeName性能不好

5.2 QualifiedNameSyntax表示泛型

SyntaxGenerator.Generic("List", SyntaxGenerator.IntType).Qualified("System.Collections.Generic")

5.3 GenericNameSyntax也可以表示泛型限定名

SyntaxGenerator.Generic("System.Collections.Generic.List", SyntaxGenerator.IntType)

5.4 GenericNameSyntax还是QualifiedNameSyntax选哪个?

首先两者能生成一样的代码

ParseTypeName得到QualifiedNameSyntax,应该代表官方意见

使用GenericNameSyntax性能好,也说得过去,带命名空间的泛型不还是泛型吗?就像白马也是马

这是问题,是个仁者见仁智者见智的问题

也可以说这不是问题,笔者觉得两个都可以用,同一个团队约定其中一种规则即可

三、ParseTypeName的坑

  1. 预定义类型

1.1 预定义类型应该使用SyntaxFactory.PredefinedType

ParseTypeName("int")的实际类型PredefinedTypeSyntax

使用SyntaxGenerator.IntType也是不错的

var type = SyntaxFactory.ParseTypeName("int");

if (type is PredefinedTypeSyntax predefinedType)

{

var predefinedType0 = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword));

Assert.Equal(predefinedType0.Keyword.Kind(), predefinedType.Keyword.Kind());

}

1.2 预定义类型调用ParseTypeName性能不好

ParseTypeName代码为SyntaxFactory.ParseTypeName("int")

PredefinedType代码为SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword))

ParseTypeName耗时为30倍,内存为12倍

如果嫌SyntaxFactory.PredefinedType表达式长,可以试试SyntaxGenerator.IntType

需要安装开源包dotnet add package Hand.Generators.EasySyntax --version 0.1.0.1-alpha

Method Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio

ParseTypeName 217.174 ns 0.6011 ns 0.6681 ns 217.703 ns 30.80 0.72 0.0575 992 B 12.40

PredefinedType 7.056 ns 0.1512 ns 0.1681 ns 6.929 ns 1.00 0.03 0.0046 80 B 1.00

  1. 普通类型

2.1 普通类型应该使用SyntaxFactory.IdentifierName

ParseTypeName("User")的实际类型IdentifierNameSyntax

var type = SyntaxFactory.ParseTypeName("User");

if (type is IdentifierNameSyntax identifierName)

{

var identifierName0 = SyntaxFactory.IdentifierName("User");

Assert.Equal(identifierName0.ToFullString(), identifierName.ToFullString());

}

2.2 普通类型调用ParseTypeName性能也不好

IdentifierName代码为SyntaxFactory.IdentifierName("User")

ParseTypeName代码为SyntaxFactory.ParseTypeName("User")

ParseTypeName耗时为46倍,内存为7倍

Method Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio

IdentifierName 10.84 ns 0.238 ns 0.275 ns 1.00 0.03 0.0083 - - 144 B 1.00

ParseTypeName 508.55 ns 0.100 ns 0.111 ns 46.95 1.15 0.0694 0.0163 0.0082 1056 B 7.33

  1. 限定名类型

就是含命名空间的类名

3.1 限定名类型应该使用SyntaxFactory.IdentifierName

ParseTypeName("Models.User")的实际类型QualifiedNameSyntax与SyntaxFactory.IdentifierName("User").Qualified("Models")一致

其实用SyntaxFactory.IdentifierName("Models.User")也是可以的

var type = SyntaxFactory.ParseTypeName("Models.User");

if (type is QualifiedNameSyntax qualifiedName)

{

var qualifiedName0 = SyntaxFactory.IdentifierName("User").Qualified("Models");

Assert.Equal(qualifiedName0.ToFullString(), qualifiedName.ToFullString());

var identifierName = SyntaxFactory.IdentifierName("Models.User");

Assert.Equal(qualifiedName0.ToFullString(), identifierName.ToFullString());

}

3.2 限定名类型调用ParseTypeName性能也不好

QualifiedName代码为SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("Models"), SyntaxFactory.IdentifierName("User"))

ParseTypeName代码为SyntaxFactory.ParseTypeName("Models.User")

IdentifierName代码为SyntaxFactory.IdentifierName("Models.User")

ParseTypeName耗时22倍,内存2倍多

IdentifierName性能最好

Method Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio

QualifiedName 30.85 ns 0.101 ns 0.116 ns 1.00 0.01 0.0231 - - 400 B 1.00

ParseTypeName 707.37 ns 0.029 ns 0.033 ns 22.93 0.08 0.0713 0.0194 0.0064 1120 B 2.80

IdentifierName 10.69 ns 0.090 ns 0.104 ns 0.35 0.00 0.0083 - - 144 B 0.36

  1. 泛型

就是含类型参数的类名

4.1 泛型应该使用SyntaxFactory.GenericName

ParseTypeName("List")的实际类型GenericNameSyntax与SyntaxGenerator.Generic("List", SyntaxGenerator.IntType)一致

SyntaxGenerator.Generic("List", SyntaxGenerator.IntType)是SyntaxFactory.GenericName("List").AddTypeArgumentListArguments(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)))的简化

var type = SyntaxFactory.ParseTypeName("List");

if (type is not GenericNameSyntax genericName)

{

var genericName0 = SyntaxGenerator.Generic("List", SyntaxGenerator.IntType);

Assert.Equal(genericName0.ToFullString(), genericName.ToFullString());

}

4.2 泛型调用ParseTypeName性能也不好

GenericName代码为SyntaxFactory.GenericName("List").AddTypeArgumentListArguments(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)))

ParseTypeName代码为SyntaxFactory.ParseTypeName("List")

ParseTypeName耗时2倍

Method Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio

GenericName 128.9 ns 0.92 ns 1.02 ns 1.00 0.01 0.0607 - 1.02 KB 1.00

ParseTypeName 284.3 ns 0.69 ns 0.74 ns 2.21 0.02 0.0658 0.0001 1.11 KB 1.08

  1. 泛型限定名

就是含命名空间的泛型

5.1 泛型限定名应该使用SyntaxFactory.IdentifierName

SyntaxFactory.ParseTypeName("System.Collections.Generic.List")的实际类型QualifiedNameSyntax与SyntaxGenerator.Generic("List", SyntaxGenerator.IntType).Qualified("System.Collections.Generic")一致

其实直接用GenericName也是可以的

var type = SyntaxFactory.ParseTypeName("System.Collections.Generic.List");

if (type is QualifiedNameSyntax qualifiedName)

{

var qualifiedName0 = SyntaxGenerator.Generic("List", SyntaxGenerator.IntType).Qualified("System.Collections.Generic");

Assert.Equal(qualifiedName0.ToFullString(), qualifiedName.ToFullString());

var genericName = SyntaxGenerator.Generic("System.Collections.Generic.List", SyntaxGenerator.IntType);

Assert.Equal(qualifiedName0.ToFullString(), genericName.ToFullString());

}

5.2 泛型限定名调用ParseTypeName性能也不好

QualifiedName代码为SyntaxGenerator.Generic("List", SyntaxGenerator.IntType).Qualified("System.Collections.Generic")

ParseTypeName代码为SyntaxFactory.ParseTypeName("System.Collections.Generic.List")

GenericName代码为SyntaxGenerator.Generic("System.Collections.Generic.List", SyntaxGenerator.IntType)

ParseTypeName耗时5倍多

直接SyntaxGenerator.Generic性能最好

Method Mean Error StdDev Median Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio

QualifiedName 72.81 ns 2.099 ns 2.333 ns 71.06 ns 1.00 0.04 0.0384 - 664 B 1.00

ParseTypeName 425.04 ns 1.605 ns 1.717 ns 426.37 ns 5.84 0.18 0.0663 0.0001 1144 B 1.72

GenericName 42.27 ns 0.637 ns 0.708 ns 42.81 ns 0.58 0.02 0.0236 - 408 B 0.61

  1. 什么时候用ParseTypeName呢

笔者觉得只要预先明确类型名的都不应该使用ParseTypeName

只有类型名传参或者从文本中解析出来才不得已用ParseTypeName

四、成员名如何表示

  1. 用QualifiedNameSyntax表示

前面提到用限定名表示属性

var user = SyntaxFactory.IdentifierName("user1");

var property = SyntaxFactory.IdentifierName("Name");

// user1.Name

var qualifiedProperty = property.Qualified(user1);

  1. 也可以用MemberAccessExpressionSyntax表示属性

var user = SyntaxFactory.IdentifierName("user1");

// user1.Name

var userName = user.Access("Name");

  1. QualifiedNameSyntax和MemberAccessExpressionSyntax哪种更好呢

从语义上来说笔者觉得MemberAccessExpressionSyntax更准确

但两者生成的代码是一样的

Access是从父对象获取子对象

一个是成员限定所属上级对象

有些场景两者都可用

有些场景用其中一个更合适

如下Case用Qualified就更巧妙

var type = SyntaxFactory.IdentifierName("Student");

var score = SyntaxFactory.IdentifierName("Score");

var other = SyntaxFactory.IdentifierName("other");

// int Compare(Student other)

var method = SyntaxGenerator.IntType.Method("Compare", type.Parameter(other.Identifier))

.ToBuilder()

// return Score - other.Score

.Return(score.Subtract(score.Qualified(other)));

五、空与null不同

  1. 类型参数空与null不同

1.1 ParameterList()定义空参数

var recordDeclaration = SyntaxGenerator.RecordDeclaration("Person")

.WithParameterList(SyntaxFactory.ParameterList())

.WithSemicolonToken();

// record Person();

1.2 null为无参数

默认就是null

var recordDeclaration = SyntaxGenerator.RecordDeclaration("Person")

.WithParameterList(null)

.WithSemicolonToken();

// record Person;

六、分号问题

一般SyntaxTree会自动处理分号

对于简化语法是需要手动加分号的

使用WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))加分号

可以简化为WithSemicolonToken()

  1. 无成员的类型需要加分号

var recordDeclaration = SyntaxGenerator.RecordStructDeclaration("Person")

.Public()

.AddParameterListParameters(

SyntaxGenerator.StringType.Parameter("Name")

)

.WithSemicolonToken();

// public record struct Person(string Name);

  1. 空方法体的方法

var method = SyntaxGenerator.IntType.Method("CreateId")

.Public()

.Partial()

.WithSemicolonToken();

// public partial int CreateId();

  1. 空的属性操作器

var property = SyntaxFactory.PropertyDeclaration(

SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)),

SyntaxFactory.Identifier("Id"))

.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))

.AddAccessorListAccessors(

SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)

.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),

SyntaxFactory.AccessorDeclaration(SyntaxKind.InitAccessorDeclaration)

.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))

);

// public int Id { get; set; }

3.1 使用EasySyntax定义空的属性操作器自动加分号

var property = SyntaxGenerator.IntType.Property("Id",

SyntaxKind.GetAccessorDeclaration,

SyntaxKind.InitAccessorDeclaration)

.Public();

// public int Id { get; set; }

七、SyntaxFactory和SyntaxKind的坑

SyntaxKind枚举有几百项

SyntaxFactory也有几百个静态方法,很多静态方法需要配合SyntaxKind使用

有时从SyntaxFactory几百中选正确方法再从SyntaxKind几百中选正确的枚举项是个不小的挑战

而且往往需要SyntaxFactory好几层嵌套

EasySyntax解决了部分问题,而且是常用的那些

很多需要嵌套也能巧妙的改成链式的

体验EasySyntax安装开源包 Hand.Generators.EasySyntax --version 0.1.0.1-alpha