如何使用 Roslyn 获取用户输入表达式的类型?
How to get the type of a user-input expression using Roslyn?
我的目标是实现一个方法 Foo
这样:
typeof(int) == Foo("1 + 1")
或
typeof(List<int>) == Foo("new List<int>()")
不执行字符串中的表达式很重要,因此我尝试使用 Roslyn 来实现这一点。到目前为止我有:
public Type Foo(string inputString)
{
var syntaxTree = CSharpSyntaxTree.ParseText($"var x = {inputString};");
var compilation = CSharpCompilation.Create("Test").AddSyntaxTrees(syntaxTree);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var typeInfo = semanticModel.GetTypeInfo(SyntaxFactory.ParseExpression(inputString));
return typeof(string);
}
现在那个函数显然目前没有做任何有用的事情,那是因为它不起作用。我收到 "Syntax node is not within syntax tree"
异常。
我尝试了一些其他(非常可怕的)迭代,包括:
var typeInfo = _semanticModel.GetTypeInfo(((FieldDeclarationSyntax)((CompilationUnitSyntax)syntaxTree.GetRoot()).Members.First()).Declaration.Variables.First().Initializer.Value);
它至少执行了,但我似乎无法从 GetTypeInfo
方法中得到任何实际的 TypeInfo
...
现在即使我设法得到它,我也不确定我能用 TypeInfo
做什么;可以把它变成 Type
还是不?
所以基本上问题归结为:
是否可以从字符串中获取 Type
?
如果是的话,我是否在朝着正确的方向模糊地前进?
您遇到的问题是 SyntaxFactory.ParseExpression(inputString)
正在完全创建一个新节点,并且与 CSharpSyntaxTree.ParseText($"var x = {inputString};")
中创建的节点不同
这是这个工作的一个例子。我有这个项目文件:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net48</TargetFramework>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.5.0" />
</ItemGroup>
</Project>
还有这个控制台应用程序
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
class Program {
static void Main(string[] args) {
Console.WriteLine(GetTypeForExpression("new System.Collections.Generic.List<System.Int32>()"));
}
public static Type GetTypeForExpression(string inputString) {
var syntaxTree = CreateSyntaxTree(inputString, out var inputExpression);
// NOTE for semantic analysis to work you will need to add references to any other assemblies
// that have types you care about.
var mscorlib = MetadataReference.CreateFromFile(typeof(string).Assembly.Location);
var compilation = CSharpCompilation.Create("Test").AddSyntaxTrees(syntaxTree).AddReferences(mscorlib);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var typeKind = semanticModel.GetTypeInfo(inputExpression).ConvertedType;
var fullyQualifiedMetadataName = typeKind.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
// C# cares about the global namespace, .NET reflection APIs do not
.Replace("global::", "")
// This handles situations where the generic representation is different in C# from the reflection APIs
.Replace(typeKind.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), typeKind.MetadataName);
return typeKind.SpecialType switch
{
// keywords are hard to get the full metadata name for
// I would just special case these
SpecialType.System_Object => typeof(object),
SpecialType.System_Boolean => typeof(bool),
SpecialType.System_Char => typeof(char),
SpecialType.System_SByte => typeof(sbyte),
SpecialType.System_Byte => typeof(byte),
SpecialType.System_Int16 => typeof(short),
SpecialType.System_UInt16 => typeof(ushort),
SpecialType.System_Int32 => typeof(int),
SpecialType.System_UInt32 => typeof(uint),
SpecialType.System_Int64 => typeof(long),
SpecialType.System_UInt64 => typeof(ulong),
SpecialType.System_Decimal => typeof(decimal),
SpecialType.System_Single => typeof(float),
SpecialType.System_Double => typeof(double),
SpecialType.System_String => typeof(string),
_ => Type.GetType(fullyQualifiedMetadataName),
};
}
private static SyntaxTree CreateSyntaxTree(string inputString, out ExpressionSyntax inputExpression) {
// Creates a tree and nodes in a valid C# expression context
var tree = CSharpSyntaxTree.ParseText($"class C{{void M(){{var x = {inputString};}}}}");
// Finds the expression inside the tree (instead of re-creating a new node unrelated to this tree)
inputExpression = tree.GetRoot()
.DescendantNodesAndSelf()
.OfType<VariableDeclarationSyntax>().Single()
.Variables.Single()
.Initializer.Value;
return tree;
}
}
我的目标是实现一个方法 Foo
这样:
typeof(int) == Foo("1 + 1")
或
typeof(List<int>) == Foo("new List<int>()")
不执行字符串中的表达式很重要,因此我尝试使用 Roslyn 来实现这一点。到目前为止我有:
public Type Foo(string inputString)
{
var syntaxTree = CSharpSyntaxTree.ParseText($"var x = {inputString};");
var compilation = CSharpCompilation.Create("Test").AddSyntaxTrees(syntaxTree);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var typeInfo = semanticModel.GetTypeInfo(SyntaxFactory.ParseExpression(inputString));
return typeof(string);
}
现在那个函数显然目前没有做任何有用的事情,那是因为它不起作用。我收到 "Syntax node is not within syntax tree"
异常。
我尝试了一些其他(非常可怕的)迭代,包括:
var typeInfo = _semanticModel.GetTypeInfo(((FieldDeclarationSyntax)((CompilationUnitSyntax)syntaxTree.GetRoot()).Members.First()).Declaration.Variables.First().Initializer.Value);
它至少执行了,但我似乎无法从 GetTypeInfo
方法中得到任何实际的 TypeInfo
...
现在即使我设法得到它,我也不确定我能用 TypeInfo
做什么;可以把它变成 Type
还是不?
所以基本上问题归结为:
是否可以从字符串中获取 Type
?
如果是的话,我是否在朝着正确的方向模糊地前进?
您遇到的问题是 SyntaxFactory.ParseExpression(inputString)
正在完全创建一个新节点,并且与 CSharpSyntaxTree.ParseText($"var x = {inputString};")
这是这个工作的一个例子。我有这个项目文件:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net48</TargetFramework>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.5.0" />
</ItemGroup>
</Project>
还有这个控制台应用程序
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
class Program {
static void Main(string[] args) {
Console.WriteLine(GetTypeForExpression("new System.Collections.Generic.List<System.Int32>()"));
}
public static Type GetTypeForExpression(string inputString) {
var syntaxTree = CreateSyntaxTree(inputString, out var inputExpression);
// NOTE for semantic analysis to work you will need to add references to any other assemblies
// that have types you care about.
var mscorlib = MetadataReference.CreateFromFile(typeof(string).Assembly.Location);
var compilation = CSharpCompilation.Create("Test").AddSyntaxTrees(syntaxTree).AddReferences(mscorlib);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var typeKind = semanticModel.GetTypeInfo(inputExpression).ConvertedType;
var fullyQualifiedMetadataName = typeKind.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
// C# cares about the global namespace, .NET reflection APIs do not
.Replace("global::", "")
// This handles situations where the generic representation is different in C# from the reflection APIs
.Replace(typeKind.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), typeKind.MetadataName);
return typeKind.SpecialType switch
{
// keywords are hard to get the full metadata name for
// I would just special case these
SpecialType.System_Object => typeof(object),
SpecialType.System_Boolean => typeof(bool),
SpecialType.System_Char => typeof(char),
SpecialType.System_SByte => typeof(sbyte),
SpecialType.System_Byte => typeof(byte),
SpecialType.System_Int16 => typeof(short),
SpecialType.System_UInt16 => typeof(ushort),
SpecialType.System_Int32 => typeof(int),
SpecialType.System_UInt32 => typeof(uint),
SpecialType.System_Int64 => typeof(long),
SpecialType.System_UInt64 => typeof(ulong),
SpecialType.System_Decimal => typeof(decimal),
SpecialType.System_Single => typeof(float),
SpecialType.System_Double => typeof(double),
SpecialType.System_String => typeof(string),
_ => Type.GetType(fullyQualifiedMetadataName),
};
}
private static SyntaxTree CreateSyntaxTree(string inputString, out ExpressionSyntax inputExpression) {
// Creates a tree and nodes in a valid C# expression context
var tree = CSharpSyntaxTree.ParseText($"class C{{void M(){{var x = {inputString};}}}}");
// Finds the expression inside the tree (instead of re-creating a new node unrelated to this tree)
inputExpression = tree.GetRoot()
.DescendantNodesAndSelf()
.OfType<VariableDeclarationSyntax>().Single()
.Variables.Single()
.Initializer.Value;
return tree;
}
}