Created
October 21, 2020 01:13
-
-
Save GrabYourPitchforks/9c9676ff5fc34bc929f42cce6586546e to your computer and use it in GitHub Desktop.
Print a class skeleton from an implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Reflection; | |
using System.Text; | |
using System.Linq; | |
using System.Diagnostics; | |
using System.Runtime.CompilerServices; | |
namespace ConsoleAppDumpCryptoApis | |
{ | |
public class Runner | |
{ | |
private HashSet<Type> _types = new HashSet<Type>(); | |
const int IndentationSpaceCount = 4; // spaces per indent | |
public void AddAssembly(Assembly asm) | |
{ | |
_types.UnionWith(asm.GetExportedTypes()); | |
} | |
public void DoIt() | |
{ | |
// First, group by namespace | |
var sortedByNamespace = _types.GroupBy(type => type.Namespace).OrderBy(group => group.Key); // sort by namespace ascending | |
// For each namespace, dump type information (sorted alphabetically within namespace) | |
foreach (var group in sortedByNamespace) | |
{ | |
Console.WriteLine($"namespace {group.Key}"); | |
Console.WriteLine("{"); | |
foreach (var type in group.OrderBy(type => type.Name)) | |
{ | |
WriteTypeInfo(type, indentationLevel: 1); | |
} | |
Console.WriteLine("}"); | |
} | |
} | |
private static void WriteTypeInfo(Type type, int indentationLevel) | |
{ | |
string padding = new string(' ', indentationLevel * IndentationSpaceCount); | |
// Console.Write(padding); | |
// Console.WriteLine(type.Name); | |
if (type.IsEnum) | |
{ | |
// WriteEnumInfo(type, indentationLevel); | |
} | |
PrintTypeHeader(type, indentationLevel); | |
} | |
private static void PrintTypeHeader(Type type, int indentationLevel) | |
{ | |
// This doesn't support generic constraints, delegates, etc. | |
// But we don't need to worry about those for now since we don't have them in the API surface we care about. | |
string padding = new string(' ', indentationLevel * IndentationSpaceCount); | |
Console.Write(padding); | |
Console.Write(IsPublic(type) ? "public " : "protected "); | |
if (type.IsValueType) | |
{ | |
if (type.IsEnum) | |
{ | |
Console.Write("enum "); | |
} | |
else | |
{ | |
if (type.IsDefined(typeof(IsReadOnlyAttribute), inherit: false)) | |
{ | |
Console.Write("readonly "); | |
} | |
if (type.IsDefined(typeof(IsByRefLikeAttribute), inherit: false)) | |
{ | |
Console.Write("ref "); | |
} | |
Console.Write("struct "); | |
} | |
} | |
else if (type.IsInterface) | |
{ | |
Console.Write("interface "); | |
} | |
else | |
{ | |
if (type.IsAbstract) | |
{ | |
if (type.IsSealed) | |
{ | |
Console.Write("static "); | |
} | |
else | |
{ | |
Console.Write("abstract "); | |
} | |
} | |
else if (type.IsSealed) | |
{ | |
Console.Write("sealed "); | |
} | |
Console.Write("class "); | |
} | |
Console.Write(GetFriendlyTypeName(type)); | |
List<Type> baseTypes = new List<Type>(); | |
if (type.IsEnum) | |
{ | |
Type underlyingType = Enum.GetUnderlyingType(type); | |
if (underlyingType != typeof(int)) | |
{ | |
baseTypes.Add(underlyingType); | |
} | |
} | |
Type baseClass = type.BaseType; | |
if (baseClass != null) | |
{ | |
if (baseClass != typeof(object) && baseClass != typeof(ValueType) && baseClass != typeof(Enum)) | |
{ | |
baseTypes.Add(baseClass); | |
} | |
baseTypes.AddRange(type.GetInterfaces().Except(baseClass.GetInterfaces()).OrderBy(type => type.Name)); | |
} | |
if (baseTypes.Count > 0) | |
{ | |
Console.Write(" : "); | |
Console.Write(string.Join(", ", baseTypes.Select(GetFriendlyTypeName))); | |
} | |
Console.WriteLine(); | |
Console.Write(padding); | |
Console.WriteLine("{"); | |
WriteMembers(type, indentationLevel + 1); | |
Console.Write(padding); | |
Console.WriteLine("}"); | |
} | |
private static void WriteMembers(Type type, int indentationLevel) | |
{ | |
string padding = new string(' ', indentationLevel * IndentationSpaceCount); | |
// Special-case enums | |
if (type.IsEnum) | |
{ | |
string[] names = Enum.GetNames(type); | |
Array values = Enum.GetValues(type); | |
for (int i = 0; i < names.Length; i++) | |
{ | |
string name = names[i]; | |
object rawValue = values.GetValue(i); | |
Console.Write($"{padding}{names[i]} = {Convert.ToInt32(rawValue)}"); | |
if (i < names.Length - 1) | |
{ | |
Console.Write(","); | |
} | |
Console.WriteLine(); | |
} | |
return; | |
} | |
// Get all members | |
List<FieldInfo> fields = new List<FieldInfo>(); | |
List<ConstructorInfo> ctors = new List<ConstructorInfo>(); | |
List<PropertyInfo> props = new List<PropertyInfo>(); | |
List<MethodInfo> operators = new List<MethodInfo>(); | |
List<MethodInfo> methods = new List<MethodInfo>(); | |
foreach (var ci in type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) | |
{ | |
if (IsPublic(ci) | IsProtected(ci)) { ctors.Add(ci); } | |
} | |
foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) | |
{ | |
if (IsPublic(pi) | IsProtected(pi)) { props.Add(pi); } | |
} | |
foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) | |
{ | |
if (!IsPublic(member) && !IsProtected(member)) { continue; } | |
if (member is FieldInfo field) | |
{ | |
fields.Add(field); | |
} | |
if (member is EventInfo evt) | |
{ | |
throw new NotSupportedException(); // Do we even have events? | |
} | |
if (member is MethodInfo mi) | |
{ | |
methods.Add(mi); | |
} | |
} | |
// print fields | |
foreach (var field in fields.OrderBy(field => field.Name)) | |
{ | |
if (IsPublic(field)) | |
{ | |
Console.Write(padding); | |
Console.Write("public "); | |
} | |
else if (IsProtected(field)) | |
{ | |
Console.Write(padding); | |
Console.Write("protected "); | |
} | |
else | |
{ | |
continue; // private | |
} | |
if (field.IsStatic) | |
{ | |
Console.Write("static "); | |
} | |
if (field.IsLiteral) | |
{ | |
Console.Write("const "); | |
} | |
if (field.IsInitOnly) | |
{ | |
Console.Write("readonly "); | |
} | |
Console.Write(GetFriendlyTypeName(field.FieldType)); | |
Console.Write(" "); | |
Console.Write(field.Name); | |
if (field.IsLiteral) | |
{ | |
Console.Write(" = "); | |
object constVal = field.GetRawConstantValue(); | |
if (constVal is string s) | |
{ | |
Console.Write("\""); | |
Console.Write(s); | |
Console.Write("\""); | |
} | |
else | |
{ | |
Console.Write(constVal); | |
} | |
} | |
Console.Write(";"); | |
Console.WriteLine(); | |
} | |
// print ctors | |
foreach (ConstructorInfo ctor in ctors) | |
{ | |
PrintVisibility(ctor, indentationLevel); | |
Console.Write(type.Name); | |
WriteMethodSignature(ctor.GetParameters()); | |
} | |
// print properties | |
foreach (PropertyInfo pi in props.OrderBy(prop => prop.Name)) | |
{ | |
PrintVisibility(pi, indentationLevel); | |
// Is this static? | |
if (pi.GetGetMethod()?.IsStatic == true || pi.GetSetMethod()?.IsStatic == true) | |
{ | |
Console.Write("static "); | |
} | |
Console.Write(GetFriendlyTypeName(pi.PropertyType)); | |
Console.Write(" "); | |
Console.Write((pi.Name == "Item") ? "this" : pi.Name); | |
ParameterInfo[] paramInfos = pi.GetIndexParameters(); | |
if (paramInfos?.Length > 0) | |
{ | |
Console.Write("["); | |
Console.Write(string.Join(", ", paramInfos.Select(GetPrettyPrintedParameter))); | |
Console.Write("]"); | |
} | |
Console.Write(" { get; "); | |
MethodInfo setMethod = pi.GetSetMethod(); | |
if (setMethod != null) | |
{ | |
if (IsPublic(pi) == IsPublic(setMethod) || IsProtected(pi) == IsProtected(setMethod)) | |
{ | |
Console.Write("set; "); // same visibility | |
} | |
else if (IsProtected(setMethod)) | |
{ | |
Console.Write("protected set; "); // reduced visibility | |
} | |
} | |
Console.WriteLine("}"); | |
} | |
// print methods | |
foreach (var mi in methods.OrderBy(mi => mi.Name)) | |
{ | |
if (mi.IsSpecialName) { continue; } // don't care | |
if (mi.DeclaringType != mi.ReflectedType) { continue; } // declared on base type | |
PrintVisibility(mi, indentationLevel); | |
// Is this static? | |
if (mi.IsStatic) | |
{ | |
Console.Write("static "); | |
} | |
if (mi.IsAbstract) | |
{ | |
Console.Write("abstract "); | |
} | |
else if (mi.IsVirtual) | |
{ | |
Console.Write("virtual "); | |
} | |
else if (mi.IsFinal) | |
{ | |
Console.Write("sealed "); | |
} | |
Console.Write(GetFriendlyTypeName(mi.ReturnType)); | |
Console.Write(" "); | |
Console.Write(mi.Name); | |
Console.Write("("); | |
Console.Write(string.Join(", ", mi.GetParameters().Select(GetPrettyPrintedParameter))); | |
Console.WriteLine(");"); | |
} | |
} | |
private static void WriteMethodSignature(ParameterInfo[] pis) | |
{ | |
Console.Write("("); | |
Console.Write(string.Join(", ", pis.Select(GetPrettyPrintedParameter))); | |
Console.Write(")"); | |
Console.WriteLine(";"); | |
} | |
private static string GetPrettyPrintedParameter(ParameterInfo pi) | |
{ | |
Type pt = pi.ParameterType; | |
string prefix = ""; | |
if (pi.IsOut) | |
{ | |
prefix = "out "; | |
pt = pt.GetElementType(); | |
} | |
else if (pt.IsByRef) | |
{ | |
prefix = "ref "; | |
pt = pt.GetElementType(); | |
if (pi.IsDefined(typeof(IsReadOnlyAttribute))) | |
{ | |
prefix = "in "; | |
} | |
} | |
string sigValue = $"{prefix}{GetFriendlyTypeName(pt)} {pi.Name}"; | |
if (pi.HasDefaultValue) | |
{ | |
sigValue += $" = {GetLiteral(pi.DefaultValue)}"; | |
} | |
return sigValue; | |
} | |
private static string GetLiteral(object obj) | |
{ | |
if (obj is null) { return "null"; } | |
else if (obj is string s) { return "\"" + s + "\""; } // assume nothing needs escaping | |
else return obj.ToString(); | |
} | |
private static void PrintVisibility(MemberInfo memberInfo, int indentationLevel) | |
{ | |
Console.Write(new string(' ', indentationLevel * IndentationSpaceCount)); | |
if (IsPublic(memberInfo)) { Console.Write("public "); } | |
else if (IsProtected(memberInfo)) { Console.Write("protected "); } | |
else { throw new InvalidOperationException("Saw a private member?"); } | |
} | |
private static void WriteEnumInfo(Type type, int indentationLevel) | |
{ | |
Debug.Assert(type.IsEnum); | |
string padding = new string(' ', indentationLevel * IndentationSpaceCount); | |
string[] names = Enum.GetNames(type); | |
Array values = Enum.GetValues(type); | |
Console.Write(padding); | |
Console.Write(IsPublic(type) ? "public enum " : "protected enum "); | |
Console.Write(GetFriendlyTypeName(type)); | |
Type underlyingType = Enum.GetUnderlyingType(type); | |
if (underlyingType != typeof(int)) | |
{ | |
Console.Write($" : {GetFriendlyTypeName(underlyingType)}"); | |
} | |
Console.WriteLine(); | |
Console.Write(padding); | |
Console.WriteLine("{"); | |
Console.WriteLine("<<VALUES>>"); | |
Console.Write(padding); | |
Console.WriteLine("}"); | |
} | |
private static string GetFriendlyTypeName(Type type) | |
{ | |
if (type == typeof(void)) { return "void"; } | |
if (type == typeof(bool)) { return "bool"; } | |
if (type == typeof(byte)) { return "byte"; } | |
if (type == typeof(sbyte)) { return "sbyte"; } | |
if (type == typeof(short)) { return "short"; } | |
if (type == typeof(ushort)) { return "ushort"; } | |
if (type == typeof(int)) { return "int"; } | |
if (type == typeof(uint)) { return "uint"; } | |
if (type == typeof(long)) { return "long"; } | |
if (type == typeof(ulong)) { return "ulong"; } | |
if (type == typeof(string)) { return "string"; } | |
if (type == typeof(char)) { return "char"; } | |
if (type == typeof(object)) { return "object"; } | |
if (type == typeof(decimal)) { return "decimal"; } | |
if (type.IsSZArray) | |
{ | |
return GetFriendlyTypeName(type.GetElementType()) + "[]"; | |
} | |
if (type.IsGenericType) | |
{ | |
return type.Name.Split('`')[0] + "<" + string.Join(", ", type.GetGenericArguments().Select(GetFriendlyTypeName)) + ">"; | |
} | |
return type.Name; | |
} | |
private static bool IsPublic(Type type) => type.IsPublic || type.IsNestedPublic; | |
private static bool IsProtected(MemberInfo member) | |
{ | |
switch (member) | |
{ | |
case FieldInfo field: return IsProtected(field); | |
case PropertyInfo prop: return IsProtected(prop); | |
case MethodInfo method: return IsProtected(method); | |
case ConstructorInfo ctor: return IsProtected(ctor); | |
default: return false; | |
} | |
} | |
private static bool IsPublic(MemberInfo member) | |
{ | |
switch (member) | |
{ | |
case FieldInfo field: return IsPublic(field); | |
case PropertyInfo prop: return IsPublic(prop); | |
case MethodInfo method: return IsPublic(method); | |
case ConstructorInfo ctor: return IsPublic(ctor); | |
default: return false; | |
} | |
} | |
private static bool IsPublic(FieldInfo field) => field.IsPublic; | |
private static bool IsPublic(ConstructorInfo ctor) => ctor.IsPublic; | |
private static bool IsPublic(MethodInfo method) => method.IsPublic; | |
private static bool IsPublic(PropertyInfo prop) | |
{ | |
MethodInfo getMethod = prop.GetGetMethod(); | |
if (getMethod != null && IsPublic(getMethod)) { return true; } | |
MethodInfo setMethod = prop.GetSetMethod(); | |
if (setMethod != null && IsPublic(setMethod)) { return true; } | |
return false; | |
} | |
private static bool IsProtected(FieldInfo field) => field.IsFamily || field.IsFamilyOrAssembly; | |
private static bool IsProtected(ConstructorInfo ctor) => ctor.IsFamily || ctor.IsFamilyOrAssembly; | |
private static bool IsProtected(MethodInfo method) => method.IsFamily || method.IsFamilyOrAssembly; | |
private static bool IsProtected(PropertyInfo prop) | |
{ | |
MethodInfo getMethod = prop.GetGetMethod(); | |
if (getMethod != null && IsProtected(getMethod)) { return true; } | |
MethodInfo setMethod = prop.GetSetMethod(); | |
if (setMethod != null && IsProtected(setMethod)) { return true; } | |
return false; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment