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

时间:2013-12-12 00:22:13

标签: c# parsing typename

如何解析格式List<int>Dictionary<string,int>或更复杂Dictionary<string,Dictionary<System.String,int[]>>的C#样式泛型类型名称。假设这些名称是字符串,实际上可能不代表现有类型。它应该很容易解析BogusClass<A,B,Vector<C>>。为了清楚起见,我对解析格式为List`1[[System.Int32]]的.NET内部类型名称并不感兴趣,而是对源代码中出现的实际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
    }
}

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

2 个答案:

答案 0 :(得分: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;
    }
}

如果我运行以下内容:

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

它正确生成以下输出,将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:
}

答案 1 :(得分:2)

好吧,使用Regex编写这个小解析类并命名为捕获组(?<Name>group),我有很多乐趣。

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

因此,鉴于经典Dictionary<string, byte[]>,您将{{1>}作为类型名称,将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();
    }
}

这是输出:

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

有一些可能存在的问题,例如多维数组。它很可能会失败,例如Input = BogusClass<A,B,Vector<C>> Output = BogusClass<A, B, Vector<C>> Graph: BogusClass A B Vector C int[,]