如何解析C#generics类型名称?
如何解析格式为List
或Dictionary
C#样式generics类型名称,甚至更复杂的Dictionary<string,Dictionary>
。 假设这些名称是字符串,实际上可能不代表现有类型。 它应该很容易解析BogusClass<A,B,Vector>
。 为了清楚List`1[[System.Int32]]
,我对解析格式为List`1[[System.Int32]]
.NET内部类型名称不感兴趣,而是对源代码中出现的实际C#类型名称感兴趣,使用点或不使用名称空间限定符符号。
正则表达式是因为这些是嵌套结构。 我想也许System.CodeDom.CodeTypeReference构造函数会为我解析它,因为它有string BaseType
和CodeTypeReferenceCollection 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 } }
框架中是否有任何现有的类来实现这种类型名称解析? 如果没有,我将如何解析这个?
回答自己的问题。 我写了下面的类来实现我需要的结果; 给它一个旋转。
public class TypeName { public string Name; public bool IsGeneric; public List ArrayDimensions; public List 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(); TypeArguments = new List(); } public static string MatchStructure( TypeName toMatch, TypeName toType ) { return null; } public override string ToString() { string str = Name; if (IsGeneric) str += "<" + string.Join( ",", TypeArguments.Select( 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( d => d.ToString() ) ) + "\r\n"; if (IsGeneric) { str += spacing + "GenericParameters: {\r\n" + string.Join( spacing + "},{\r\n", TypeArguments.Select( 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,int[],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: }
好吧,我使用Regex
和命名捕获组(?
编写这个小解析类有很多乐趣。
我的方法是每个’类型定义’字符串可以分解为以下一组: 类型名称 , 可选的 通用类型和可选的 数组标记 ‘[]’。
因此,给定经典的Dictionary
您将使用Dictionary
作为类型名称和string, byte[]
作为内部generics类型字符串。
我们可以在逗号(’,’)字符上拆分内部generics类型,并使用相同的正则Regex
递归地解析每个类型字符串。 应将每个成功的解析添加到父类型信息中,并且可以构建树层次结构。
在前面的例子中,我们最终会得到一个{string, byte[]}
数组来解析。 这两个都很容易解析并设置为Dictionary
的内部类型的一部分。
在ToString()
它只是递归输出每个类型的友好名称,包括内部类型。 所以Dictionary
会输出他的类型名称,并遍历所有内部类型,输出他们的类型名称等等。
class TypeInformation { static readonly Regex TypeNameRegex = new Regex(@"^(?[a-zA-Z0-9_]+)(<(?[a-zA-Z0-9_,\<\>\s\[\]]+)>)?(?(\[\]))?$", RegexOptions.Compiled); readonly List innerTypes = new List (); public string TypeName { get; private set; } public bool IsArray { get; private set; } public bool IsGeneric { get { return innerTypes.Count > 0; } } public IEnumerable InnerTypes { get { return innerTypes; } } private void AddInnerType(TypeInformation type) { innerTypes.Add(type); } private static IEnumerable SplitByComma(string value) { var strings = new List (); 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>"; 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> Output = BogusClass> Graph: BogusClass A B Vector C
存在一些可能的延迟问题,例如多维数组。 它很可能会失败,比如int[,]
或string[][]
。
- SSIS – 进程无法访问该文件,因为它正由另一个进程使用
- 覆盖ASP.NET Core MVC 1.0中的全局授权filter
- ThreadPool.QueueUserWorkItem,带有lambda表达式和匿名方法
- 序列化List 的XmlSerializer的构造函数在与XmlAttributeOverrides一起使用时会抛出InvalidOperationException
- 尝试调试ASP.NET网站时Visual Studio 2013 sp1挂起?
- 如何在C#中将int 转换为byte
- 在.Net中概述GDI +的路径
- System.Net.WebException:基础连接已关闭:连接意外关闭
- C#中的网络摄像头使用