|
| 1 | +using System; |
| 2 | +using System.Reflection; |
| 3 | +using System.Text; |
| 4 | + |
| 5 | +namespace CSharpTypePrinter |
| 6 | +{ |
| 7 | + public static class TypePrinter |
| 8 | + { |
| 9 | + public static string ToCSharpCode(this Type type, |
| 10 | + bool stripNamespace = false, Func<Type, string, string> printType = null, bool printGenericTypeArgs = false) |
| 11 | + { |
| 12 | + if (type.IsGenericParameter) |
| 13 | + return !printGenericTypeArgs ? string.Empty |
| 14 | + : (printType?.Invoke(type, type.Name) ?? type.Name); |
| 15 | + |
| 16 | + Type arrayType = null; |
| 17 | + if (type.IsArray) |
| 18 | + { |
| 19 | + // store the original type for the later and process its element type further here |
| 20 | + arrayType = type; |
| 21 | + type = type.GetElementType(); |
| 22 | + } |
| 23 | + |
| 24 | + // the default handling of the built-in types |
| 25 | + string buildInTypeString = null; |
| 26 | + if (type == typeof(void)) |
| 27 | + buildInTypeString = "void"; |
| 28 | + if (type == typeof(object)) |
| 29 | + buildInTypeString = "object"; |
| 30 | + if (type == typeof(bool)) |
| 31 | + buildInTypeString = "bool"; |
| 32 | + if (type == typeof(int)) |
| 33 | + buildInTypeString = "int"; |
| 34 | + if (type == typeof(short)) |
| 35 | + buildInTypeString = "short"; |
| 36 | + if (type == typeof(byte)) |
| 37 | + buildInTypeString = "byte"; |
| 38 | + if (type == typeof(double)) |
| 39 | + buildInTypeString = "double"; |
| 40 | + if (type == typeof(float)) |
| 41 | + buildInTypeString = "float"; |
| 42 | + if (type == typeof(char)) |
| 43 | + buildInTypeString = "char"; |
| 44 | + if (type == typeof(string)) |
| 45 | + buildInTypeString = "string"; |
| 46 | + |
| 47 | + if (buildInTypeString != null) |
| 48 | + return printType?.Invoke(arrayType ?? type, buildInTypeString) ?? buildInTypeString; |
| 49 | + |
| 50 | + var parentCount = 0; |
| 51 | + for (var ti = type.GetTypeInfo(); ti.IsNested; ti = ti.DeclaringType.GetTypeInfo()) |
| 52 | + ++parentCount; |
| 53 | + |
| 54 | + Type[] parentTypes = null; |
| 55 | + if (parentCount > 0) |
| 56 | + { |
| 57 | + parentTypes = new Type[parentCount]; |
| 58 | + var pt = type.DeclaringType; |
| 59 | + for (var i = 0; i < parentTypes.Length; i++, pt = pt.DeclaringType) |
| 60 | + parentTypes[i] = pt; |
| 61 | + } |
| 62 | + |
| 63 | + var typeInfo = type.GetTypeInfo(); |
| 64 | + Type[] typeArgs = null; |
| 65 | + var isTypeClosedGeneric = false; |
| 66 | + if (type.IsGenericType) |
| 67 | + { |
| 68 | + isTypeClosedGeneric = !typeInfo.IsGenericTypeDefinition; |
| 69 | + typeArgs = isTypeClosedGeneric ? typeInfo.GenericTypeArguments : typeInfo.GenericTypeParameters; |
| 70 | + } |
| 71 | + |
| 72 | + var typeArgsConsumedByParentsCount = 0; |
| 73 | + var s = new StringBuilder(); |
| 74 | + if (!stripNamespace) |
| 75 | + s.Append(type.Namespace).Append('.'); |
| 76 | + |
| 77 | + if (parentTypes != null) |
| 78 | + { |
| 79 | + for (var p = parentTypes.Length - 1; p >= 0; --p) |
| 80 | + { |
| 81 | + var parentType = parentTypes[p]; |
| 82 | + if (!parentType.IsGenericType) |
| 83 | + { |
| 84 | + s.Append(parentType.Name).Append('.'); |
| 85 | + } |
| 86 | + else |
| 87 | + { |
| 88 | + var parentTypeInfo = parentType.GetTypeInfo(); |
| 89 | + Type[] parentTypeArgs = null; |
| 90 | + if (parentTypeInfo.IsGenericTypeDefinition) |
| 91 | + { |
| 92 | + parentTypeArgs = parentTypeInfo.GenericTypeParameters; |
| 93 | + |
| 94 | + // replace the open parent args with the closed child args, |
| 95 | + // and close the parent |
| 96 | + if (isTypeClosedGeneric) |
| 97 | + for (var t = 0; t < parentTypeArgs.Length; ++t) |
| 98 | + parentTypeArgs[t] = typeArgs[t]; |
| 99 | + |
| 100 | + var parentTypeArgCount = parentTypeArgs.Length; |
| 101 | + if (typeArgsConsumedByParentsCount > 0) |
| 102 | + { |
| 103 | + int ownArgCount = parentTypeArgCount - typeArgsConsumedByParentsCount; |
| 104 | + if (ownArgCount == 0) |
| 105 | + parentTypeArgs = null; |
| 106 | + else |
| 107 | + { |
| 108 | + var ownArgs = new Type[ownArgCount]; |
| 109 | + for (var a = 0; a < ownArgs.Length; ++a) |
| 110 | + ownArgs[a] = parentTypeArgs[a + typeArgsConsumedByParentsCount]; |
| 111 | + parentTypeArgs = ownArgs; |
| 112 | + } |
| 113 | + } |
| 114 | + typeArgsConsumedByParentsCount = parentTypeArgCount; |
| 115 | + } |
| 116 | + else |
| 117 | + { |
| 118 | + parentTypeArgs = parentTypeInfo.GenericTypeArguments; |
| 119 | + } |
| 120 | + |
| 121 | + var parentTickIndex = parentType.Name.IndexOf('`'); |
| 122 | + s.Append(parentType.Name.Substring(0, parentTickIndex)); |
| 123 | + |
| 124 | + // The owned parentTypeArgs maybe empty because all args are defined in the parent's parents |
| 125 | + if (parentTypeArgs?.Length > 0) |
| 126 | + { |
| 127 | + s.Append('<'); |
| 128 | + for (var t = 0; t < parentTypeArgs.Length; ++t) |
| 129 | + (t == 0 ? s : s.Append(", ")) |
| 130 | + .Append(parentTypeArgs[t].ToCSharpCode(stripNamespace, printType, printGenericTypeArgs)); |
| 131 | + s.Append('>'); |
| 132 | + } |
| 133 | + s.Append('.'); |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + if (typeArgs != null && typeArgsConsumedByParentsCount < typeArgs.Length) |
| 139 | + { |
| 140 | + var tickIndex = type.Name.IndexOf('`'); |
| 141 | + s.Append(type.Name.Substring(0, tickIndex)).Append('<'); |
| 142 | + for (var i = 0; i < typeArgs.Length - typeArgsConsumedByParentsCount; ++i) |
| 143 | + (i == 0 ? s : s.Append(", ")) |
| 144 | + .Append(typeArgs[i + typeArgsConsumedByParentsCount] |
| 145 | + .ToCSharpCode(stripNamespace, printType, printGenericTypeArgs)); |
| 146 | + s.Append('>'); |
| 147 | + } |
| 148 | + else |
| 149 | + { |
| 150 | + s.Append(type.Name); |
| 151 | + } |
| 152 | + |
| 153 | + if (arrayType != null) |
| 154 | + s.Append("[]"); |
| 155 | + |
| 156 | + return printType?.Invoke(arrayType ?? type, s.ToString()) ?? s.ToString(); |
| 157 | + } |
| 158 | + } |
| 159 | +} |
0 commit comments