Как получить тип пользовательского ввода выражения, используя Roslyn? - PullRequest
0 голосов
/ 07 апреля 2020

Моя цель - реализовать метод 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);

, который хотя бы выполнялся, но я не могу получить какой-либо фактический TypeInfo от метода GetTypeInfo ...

Теперь, даже если мне это удалось, я не уверен, что я могу сделать с TypeInfo; может превратить его в Type или нет?

Так что по сути вопрос сводится к следующему: возможно ли получить Type из строки? Если это так, то я смутно иду в правильном направлении?

1 Ответ

0 голосов
/ 09 апреля 2020

Проблема, с которой вы столкнулись, заключается в том, что 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;
    }
}
...