Skip to content

Instantly share code, notes, and snippets.

@GrabYourPitchforks
Created October 21, 2020 01:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GrabYourPitchforks/9c9676ff5fc34bc929f42cce6586546e to your computer and use it in GitHub Desktop.
Save GrabYourPitchforks/9c9676ff5fc34bc929f42cce6586546e to your computer and use it in GitHub Desktop.
Print a class skeleton from an implementation
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