Skip to content

Commit 28a26a9

Browse files
author
maximv
committed
adding the whe working code
1 parent 1740f83 commit 28a26a9

File tree

6 files changed

+331
-4
lines changed

6 files changed

+331
-4
lines changed

CSharpTypePrinter.sln

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 15
4+
VisualStudioVersion = 15.0.26124.0
5+
MinimumVisualStudioVersion = 15.0.26124.0
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{12F98C30-0C59-4718-9797-B8DDEF639C9B}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpTypePrinter", "src\CSharpTypePrinter\CSharpTypePrinter.csproj", "{B5C17341-5E2D-4B89-A52E-28A7942215B9}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2CA87D6C-7810-4FB2-829F-A963D8339B30}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpTypePrinter.Tests", "test\CSharpTypePrinter.Tests\CSharpTypePrinter.Tests.csproj", "{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}"
13+
EndProject
14+
Global
15+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
16+
Debug|Any CPU = Debug|Any CPU
17+
Debug|x64 = Debug|x64
18+
Debug|x86 = Debug|x86
19+
Release|Any CPU = Release|Any CPU
20+
Release|x64 = Release|x64
21+
Release|x86 = Release|x86
22+
EndGlobalSection
23+
GlobalSection(SolutionProperties) = preSolution
24+
HideSolutionNode = FALSE
25+
EndGlobalSection
26+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
27+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
29+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Debug|x64.ActiveCfg = Debug|Any CPU
30+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Debug|x64.Build.0 = Debug|Any CPU
31+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Debug|x86.ActiveCfg = Debug|Any CPU
32+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Debug|x86.Build.0 = Debug|Any CPU
33+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
34+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Release|Any CPU.Build.0 = Release|Any CPU
35+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Release|x64.ActiveCfg = Release|Any CPU
36+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Release|x64.Build.0 = Release|Any CPU
37+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Release|x86.ActiveCfg = Release|Any CPU
38+
{B5C17341-5E2D-4B89-A52E-28A7942215B9}.Release|x86.Build.0 = Release|Any CPU
39+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Debug|Any CPU.Build.0 = Debug|Any CPU
41+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Debug|x64.ActiveCfg = Debug|Any CPU
42+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Debug|x64.Build.0 = Debug|Any CPU
43+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Debug|x86.ActiveCfg = Debug|Any CPU
44+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Debug|x86.Build.0 = Debug|Any CPU
45+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Release|Any CPU.ActiveCfg = Release|Any CPU
46+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Release|Any CPU.Build.0 = Release|Any CPU
47+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Release|x64.ActiveCfg = Release|Any CPU
48+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Release|x64.Build.0 = Release|Any CPU
49+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Release|x86.ActiveCfg = Release|Any CPU
50+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715}.Release|x86.Build.0 = Release|Any CPU
51+
EndGlobalSection
52+
GlobalSection(NestedProjects) = preSolution
53+
{B5C17341-5E2D-4B89-A52E-28A7942215B9} = {12F98C30-0C59-4718-9797-B8DDEF639C9B}
54+
{8B526BC3-7F4E-4C95-A336-6E88CCA3E715} = {2CA87D6C-7810-4FB2-829F-A963D8339B30}
55+
EndGlobalSection
56+
EndGlobal

README.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,31 @@
11
# CSharpTypePrinter
22

3-
Prints `System.Type` object as a valid C# code, e.g. `typeof(A<X>.B<Y>.C)` to `"A<X>.B<Y>.C"`
3+
Prints a `System.Type` object as a valid C# code, e.g. prints `typeof(A<X>.B<Y>.C)` as a `"A<X>.B<Y>.C"`
44

5-
It happens that the code for this is the write-only pile of details.
6-
I am using it in a three of my projects.
5+
It happens that the code for this is the complex pile of details especially if we talk about nested generics.
76

8-
**Code will follow soon...**
7+
So I wanted to automate it and get and the robust implementation. A similar code is used Today by three of my projects: [DryIoc](https://github.com/dadhi/DryIoc), [FastExpressionCompiler](https://github.com/dadhi/FastExpressionCompiler), [ImTools](https://github.com/dadhi/ImTools).
8+
9+
The library contains a single extension method:
10+
11+
```cs
12+
public static class TypePrinter
13+
{
14+
public static string ToCSharpCode(this Type type,
15+
bool stripNamespace = false,
16+
Func<Type, string, string> printType = null,
17+
bool printGenericTypeArgs = false)
18+
{
19+
//:-)
20+
}
21+
}
22+
```
23+
24+
The options include:
25+
26+
- `stripNamespace` self explanatory.
27+
- `printType` function may configure the final result given the input type and the output string.
28+
- `printGenericTypeArgs` will output open-generic type as `Blah<T>` instead of `Blah<>`. The default value was selected because my own primary use -case is the type inside the `typeof()` where `typeof(Blah<>)` is the valid and the `typeof(Blah<T>)` is not.
29+
30+
31+
Happy coding!
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
</Project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>netcoreapp3.1</TargetFramework>
4+
<IsPackable>false</IsPackable>
5+
<IsTestProject>true</IsTestProject>
6+
</PropertyGroup>
7+
<ItemGroup Condition="'$(IsTestProject)' == 'true'">
8+
<PackageReference Include="NUnit" Version="3.12.0"/>
9+
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0"/>
10+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1"/>
11+
</ItemGroup>
12+
<ItemGroup>
13+
<ProjectReference Include="..\..\src\CSharpTypePrinter\CSharpTypePrinter.csproj"/>
14+
</ItemGroup>
15+
</Project>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using NUnit.Framework;
3+
4+
namespace CSharpTypePrinter.Tests
5+
{
6+
[TestFixture]
7+
public class Tests
8+
{
9+
[Test]
10+
public void Test_triple_nested_non_generic()
11+
{
12+
var s = typeof(A<int>.B<string>.Z).ToCSharpCode(true);
13+
Assert.AreEqual("Tests.A<int>.B<string>.Z", s);
14+
15+
s = typeof(A<int>.B<string>.Z).ToCSharpCode();
16+
Assert.AreEqual("CSharpTypePrinter.Tests.Tests.A<int>.B<string>.Z", s);
17+
18+
s = typeof(A<int>.B<string>.Z[]).ToCSharpCode(true);
19+
Assert.AreEqual("Tests.A<int>.B<string>.Z[]", s);
20+
21+
s = typeof(A<int>.B<string>.Z[]).ToCSharpCode(true, (_, x) => x.Replace("Tests.", ""));
22+
Assert.AreEqual("A<int>.B<string>.Z[]", s);
23+
}
24+
25+
[Test]
26+
public void Test_triple_nested_open_generic()
27+
{
28+
var s = typeof(A<>).ToCSharpCode(true, (_, x) => x.Replace("Tests.", ""));
29+
Assert.AreEqual("A<>", s);
30+
31+
s = typeof(A<>).ToCSharpCode(true, (_, x) => x.Replace("Tests.", ""), true);
32+
Assert.AreEqual("A<X>", s);
33+
34+
s = typeof(A<>.B<>).ToCSharpCode(true, (_, x) => x.Replace("Tests.", ""));
35+
Assert.AreEqual("A<>.B<>", s);
36+
37+
s = typeof(A<>.B<>.Z).ToCSharpCode(true, (_, x) => x.Replace("Tests.", ""));
38+
Assert.AreEqual("A<>.B<>.Z", s);
39+
40+
s = typeof(A<>.B<>.Z).ToCSharpCode(true, (_, x) => x.Replace("Tests.", ""), true);
41+
Assert.AreEqual("A<X>.B<Y>.Z", s);
42+
}
43+
44+
[Test]
45+
public void Test_non_generic_classes()
46+
{
47+
var s = typeof(A.B.C).ToCSharpCode(true, (_, x) => x.Replace("Tests.", ""));
48+
Assert.AreEqual("A.B.C", s);
49+
}
50+
51+
class A
52+
{
53+
public class B
54+
{
55+
public class C { }
56+
}
57+
}
58+
59+
class A<X>
60+
{
61+
public class B<Y>
62+
{
63+
public class Z { }
64+
}
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)