简体   繁体   中英

How to get the type of a user-input expression using Roslyn?

My aim is to implement a method Foo such that:

typeof(int) == Foo("1 + 1")

or

typeof(List<int>) == Foo("new List<int>()")

It's important that the expression within the string is not executed, so I am trying to use Roslyn to achieve this. So far I have got:

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);
}

Now that function obviously doesn't do anything useful currently, that's because it doesn't work. I get a "Syntax node is not within syntax tree" exception. I have tried some other (pretty terrifying) iterations, including:

var typeInfo = _semanticModel.GetTypeInfo(((FieldDeclarationSyntax)((CompilationUnitSyntax)syntaxTree.GetRoot()).Members.First()).Declaration.Variables.First().Initializer.Value);

which did at least execute, but I can't seem to get any actual TypeInfo from the GetTypeInfo method...

Now even if I did manage to get it, I'm not sure what I can do with a TypeInfo ; can turn it into a Type or not?

So essentially the question boils down to: Is it possible to get a Type from a string? If it is am I going even vaguely in the right direction?

The issue you are having is that SyntaxFactory.ParseExpression(inputString) is creating a new node entirely and is not the same as the node created in CSharpSyntaxTree.ParseText($"var x = {inputString};")

Here is an example of this working. I have this project file:

<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>

And this console app

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;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM