如何解析C#泛型类型名称?

Tri*_*nko 7 c# parsing typename

如何可以解析C#风格格式的泛型类型名称List<int>或者Dictionary<string,int>甚至更复杂Dictionary<string,Dictionary<System.String,int[]>>.假设这些名称是字符串,实际上可能不代表现有类型.它应该很容易解析BogusClass<A,B,Vector<C>>.为了清楚起见,我对解析格式的.NET内部类型名称并不感兴趣List`1[[System.Int32]],而是对源代码中出现的实际C#类型名称感兴趣,使用点表示法有或没有名称空间限定符.

正则表达式是因为这些是嵌套结构.我想也许System.CodeDom.CodeTypeReference构造函数会为我解析它,因为它有string BaseTypeCodeTypeReferenceCollection TypeArguments成员,但那些显然需要手动设置.

CodeTypeReference是我需要的一种结构:

class TypeNameStructure
{
    public string Name;
    public TypeNameStructure[] GenericTypeArguments;
    public bool IsGenericType{get;}
    public bool IsArray{get;} //would be nice to detect this as well

    public TypeNameStructure( string friendlyCSharpName )
    {
       //Parse friendlyCSharpName into name and generic type arguments recursively
    }
}
Run Code Online (Sandbox Code Playgroud)

框架中是否有任何现有的类来实现这种类型名称解析?如果没有,我将如何解析这个?

Eri*_*rik 5

Regex嗯,我在使用命名捕获组编写这个小解析类时获得了很多乐趣(?<Name>group)

我的方法是每个“类型定义”字符串可以分解为以下一组:类型名称可选的 通用类型可选的 数组标记“[]”。

因此,给定经典,Dictionary<string, byte[]>您将拥有Dictionary类型名称内部泛型类型string, byte[]字符串。

我们可以在逗号(',')字符上拆分内部泛型类型,并使用相同的Regex. 每个成功的解析都应该添加到父类型信息中,并且您可以构建树层次结构。

在前面的示例中,我们最终会得到一个{string, byte[]}要解析的数组。这两个都可以轻松解析并设置为 的Dictionary内部类型的一部分。

ToString()只是递归输出每种类型的友好名称(包括内部类型)的问题。因此Dictionary将输出他的类型名称,并迭代所有内部类型,输出它们的类型名称等等。

class TypeInformation
{
    static readonly Regex TypeNameRegex = new Regex(@"^(?<TypeName>[a-zA-Z0-9_]+)(<(?<InnerTypeName>[a-zA-Z0-9_,\<\>\s\[\]]+)>)?(?<Array>(\[\]))?$", RegexOptions.Compiled);

    readonly List<TypeInformation> innerTypes = new List<TypeInformation>();

    public string TypeName
    {
        get;
        private set;
    }

    public bool IsArray
    {
        get;
        private set;
    }

    public bool IsGeneric
    {
        get { return innerTypes.Count > 0; }
    }

    public IEnumerable<TypeInformation> InnerTypes
    {
        get { return innerTypes; }
    }

    private void AddInnerType(TypeInformation type)
    {
        innerTypes.Add(type);
    }

    private static IEnumerable<string> SplitByComma(string value)
    {
        var strings = new List<string>();
        var sb = new StringBuilder();
        var level = 0;

        foreach (var c in value)
        {
            if (c == ',' && level == 0)
            {
                strings.Add(sb.ToString());
                sb.Clear();
            }
            else
            {
                sb.Append(c);
            }

            if (c == '<')
                level++;

            if(c == '>')
                level--;
        }

        strings.Add(sb.ToString());

        return strings;
    }

    public static bool TryParse(string friendlyTypeName, out TypeInformation typeInformation)
    {
        typeInformation = null;

        // Try to match the type to our regular expression.
        var match = TypeNameRegex.Match(friendlyTypeName);

        // If that fails, the format is incorrect.
        if (!match.Success)
            return false;

        // Scrub the type name, inner type name, and array '[]' marker (if present).
        var typeName = match.Groups["TypeName"].Value;
        var innerTypeFriendlyName = match.Groups["InnerTypeName"].Value;
        var isArray = !string.IsNullOrWhiteSpace(match.Groups["Array"].Value);

        // Create the root type information.
        TypeInformation type = new TypeInformation
        {
            TypeName = typeName,
            IsArray = isArray
        };

        // Check if we have an inner type name (in the case of generics).
        if (!string.IsNullOrWhiteSpace(innerTypeFriendlyName))
        {
            // Split each type by the comma character.
            var innerTypeNames = SplitByComma(innerTypeFriendlyName);

            // Iterate through all inner type names and attempt to parse them recursively.
            foreach (string innerTypeName in innerTypeNames)
            {
                TypeInformation innerType = null;
                var trimmedInnerTypeName = innerTypeName.Trim();
                var success = TypeInformation.TryParse(trimmedInnerTypeName, out innerType);

                // If the inner type fails, so does the parent.
                if (!success)
                    return false;

                // Success! Add the inner type to the parent.
                type.AddInnerType(innerType);
            }
        }

        // Return the parsed type information.
        typeInformation = type;
        return true;
    }

    public override string ToString()
    {
        // Create a string builder with the type name prefilled.
        var sb = new StringBuilder(this.TypeName);

        // If this type is generic (has inner types), append each recursively.
        if (this.IsGeneric)
        {
            sb.Append("<");

            // Get the number of inner types.
            int innerTypeCount = this.InnerTypes.Count();

            // Append each inner type's friendly string recursively.
            for (int i = 0; i < innerTypeCount; i++)
            {
                sb.Append(innerTypes[i].ToString());

                // Check if we need to add a comma to separate from the next inner type name.
                if (i + 1 < innerTypeCount)
                    sb.Append(", ");
            }

            sb.Append(">");
        }

        // If this type is an array, we append the array '[]' marker.
        if (this.IsArray)
            sb.Append("[]");

        return sb.ToString();
    }
}
Run Code Online (Sandbox Code Playgroud)

我制作了一个控制台应用程序来测试它,它似乎适用于我抛出的大多数情况。

这是代码:

class MainClass
{
    static readonly int RootIndentLevel = 2;
    static readonly string InputString = @"BogusClass<A,B,Vector<C>>";

    public static void Main(string[] args)
    {
        TypeInformation type = null;

        Console.WriteLine("Input  = {0}", InputString);

        var success = TypeInformation.TryParse(InputString, out type);

        if (success)
        {
            Console.WriteLine("Output = {0}", type.ToString());

            Console.WriteLine("Graph:");
            OutputGraph(type, RootIndentLevel);
        }
        else
            Console.WriteLine("Parsing error!");
    }

    static void OutputGraph(TypeInformation type, int indentLevel = 0)
    {
        Console.WriteLine("{0}{1}{2}", new string(' ', indentLevel), type.TypeName, type.IsArray ? "[]" : string.Empty);

        foreach (var innerType in type.InnerTypes)
            OutputGraph(innerType, indentLevel + 2);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是输出:

Input  = BogusClass<A,B,Vector<C>>
Output = BogusClass<A, B, Vector<C>>
Graph:
  BogusClass
    A
    B
    Vector
      C
Run Code Online (Sandbox Code Playgroud)

有一些可能挥之不去的问题,例如多维数组。它很可能会在诸如int[,]或 之类的事情上失败string[][]


Tri*_*nko 4

回答自己的问题。我写了下面的类达到了我需要的结果;试一试。

public class TypeName
{
    public string Name;
    public bool IsGeneric;
    public List<ArrayDimension> ArrayDimensions;
    public List<TypeName> TypeArguments;

    public class ArrayDimension
    {
        public int Dimensions;

        public ArrayDimension()
        {
            Dimensions = 1;
        }

        public override string ToString()
        {
            return "[" + new String(',', Dimensions - 1) + "]";
        }
    }

    public TypeName()
    {
        Name = null;
        IsGeneric = false;
        ArrayDimensions = new List<ArrayDimension>();
        TypeArguments = new List<TypeName>();
    }

    public static string MatchStructure( TypeName toMatch, TypeName toType )
    {
        return null;
    }

    public override string ToString()
    {
        string str = Name;
        if (IsGeneric)
            str += "<" + string.Join( ",", TypeArguments.Select<TypeName,string>( tn => tn.ToString() ) ) + ">";
        foreach (ArrayDimension d in ArrayDimensions)
            str += d.ToString();
        return str;
    }

    public string FormatForDisplay( int indent = 0 )
    {
        var spacing = new string(' ', indent );
        string str = spacing + "Name: " + Name + "\r\n" +
        spacing + "IsGeneric: " + IsGeneric + "\r\n" +
        spacing + "ArraySpec: " + string.Join( "", ArrayDimensions.Select<ArrayDimension,string>( d => d.ToString() ) ) + "\r\n";
        if (IsGeneric)
        {
            str += spacing + "GenericParameters: {\r\n" + string.Join( spacing + "},{\r\n", TypeArguments.Select<TypeName,string>( t => t.FormatForDisplay( indent + 4 ) ) ) + spacing + "}\r\n";
        }
        return str;
    }

    public static TypeName Parse( string name )
    {
        int pos = 0;
        bool dummy;
        return ParseInternal( name, ref pos, out dummy );
    }

    private static TypeName ParseInternal( string name, ref int pos, out bool listTerminated )
    {
        StringBuilder sb = new StringBuilder();
        TypeName tn = new TypeName();
        listTerminated = true;
        while (pos < name.Length)
        {
            char c = name[pos++];
            switch (c)
            {
                case ',':
                    if (tn.Name == null)
                        tn.Name = sb.ToString();
                    listTerminated = false;
                    return tn;
                case '>':
                    if (tn.Name == null)
                        tn.Name = sb.ToString();
                    listTerminated = true;
                    return tn;
                case '<':
                {
                    tn.Name = sb.ToString();
                    tn.IsGeneric = true;
                    sb.Length = 0;
                    bool terminated = false;
                    while (!terminated)
                        tn.TypeArguments.Add( ParseInternal( name, ref pos, out terminated ) );
                    var t = name[pos-1];
                    if (t == '>')
                        continue;
                    else
                        throw new Exception( "Missing closing > of generic type list." );
                }
                case '[':
                    ArrayDimension d = new ArrayDimension();
                    tn.ArrayDimensions.Add( d );
                analyzeArrayDimension: //label for looping over multidimensional arrays
                    if (pos < name.Length)
                    {
                        char nextChar = name[pos++];
                        switch (nextChar)
                        {
                            case ']':
                                continue; //array specifier terminated
                            case ',': //multidimensional array
                                d.Dimensions++;
                                goto analyzeArrayDimension;
                            default:
                                throw new Exception( @"Expecting ""]"" or "","" after ""["" for array specifier but encountered """ + nextChar + @"""." );
                        }
                    }
                    throw new Exception( "Expecting ] or , after [ for array type, but reached end of string." );
                default:
                    sb.Append(c);
                    continue;
            }
        }
        if (tn.Name == null)
            tn.Name = sb.ToString();
        return tn;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我运行以下命令:

 Console.WriteLine( TypeName.Parse( "System.Collections.Generic.Dictionary<Vector<T>,int<long[]>[],bool>" ).ToString() );
Run Code Online (Sandbox Code Playgroud)

它正确地生成以下输出,将 TypeName 表示为字符串:

Name: System.Collections.Generic.Dictionary
IsGeneric: True
ArraySpec:
GenericParameters: {
    Name: Vector
    IsGeneric: True
    ArraySpec:
    GenericParameters: {
        Name: T
        IsGeneric: False
        ArraySpec:
    }
},{
    Name: int
    IsGeneric: True
    ArraySpec: []
    GenericParameters: {
        Name: long
        IsGeneric: False
        ArraySpec: []
    }
},{
    Name: bool
    IsGeneric: False
    ArraySpec:
}
Run Code Online (Sandbox Code Playgroud)