Roslyn で数値リテラルを扱う備忘録

.NET Compiler Platform (Roslyn) で数値リテラルを扱うときのメモ。

Syntax Tree では難しく、 Semantic Model では簡単、というお話。

by Syntax Tree

Syntax Tree を使って数値リテラルを見る際に気を付けなければならないのは、負数の扱いとサフィックス(数字の後ろにつく f とか m とか)です。

負の定数の構文木 負の定数の構文木
リテラルだけを見た場合 リテラルだけを見た場合

Syntax Tree では、マイナスの符号は UnaryMinusExpression として、 LiteralExpression とは独立しています。そのため、 LiteralExpression だけを持ってくると符号が消えて、その正の値のノードのみが処理対象になってしまいます。

また、 NumericLiteralExpression#ToFullString() して double.Parse() などすると、サフィックスがついている場合はエラーになります。この際は、 LiteralExpression#Token プロパティの Value によって実際の値を取得できるので、それを使います。(キャスト等はされていませんが)

by Semantic Model

Semantic Model を使って数値リテラルを調べるのは簡単で、 CompilationContext から取得した SemanticModel の、 GetConstantValue() メソッドを使います。ここに定数、またはそれを判定したいノードを与えます。そうすると Nullable<T> っぽい Optional<T> で値を返してくれるようです。

ノードの選別が面倒なこともあり、配列の初期化部分で値を定義しています。ソースコード等はこちらから

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
var code = @"
class Class1
{
void Method1()
{
const int A = 1;
var foo = 1;
var array = new object[]
{
-1.2f*2+A,
1.0+foo,
10.1,
20m,
1,
(byte)255,
'c',
nameof(Class1)+""!""
};
}
}";

var tree = CSharpSyntaxTree.ParseText(code);
var mscorlib = MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location);
var compilation = CSharpCompilation.Create("NumericLiteral", new[] { tree }, new[] { mscorlib });
var model = compilation.GetSemanticModel(tree);

var values = tree.GetRoot().DescendantNodes().OfType<ArrayCreationExpressionSyntax>().First()
.ChildNodes().OfType<InitializerExpressionSyntax>().First()
.ChildNodes();
foreach (var item in values)
{
var constant = model.GetConstantValue(item);
Console.WriteLine($"{item.WithoutTrivia().ToFullString()}\n" +
$" -> {(constant.HasValue ? $"{constant.Value} <{constant.Value.GetType()}>" : "null")}\n");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-1.2f*2+A
-> -1.4 <System.Single>

1.0+foo
-> null

10.1
-> 10.1 <System.Double>

20m
-> 20 <System.Decimal>

1
-> 1 <System.Int32>

(byte)255
-> 255 <System.Byte>

'c'
-> c <System.Char>

nameof(Class1)+"!"
-> Class1! <System.String>

複雑な値でも型までしっかり判定してくれます。 null は、見たノードが定数でないと判断された場合に出力( Value プロパティがnull )されます。