公司产品对外公布的接口, 评审后才能发布, 然后要求生成接口文档(去除注释, 这样更能检查接口命名是否合理).
之前用的是微软的一个免费工具, 但好久都没有更新了, 对新一点的C#语法不支持, 生成的文档是错误的, 如果不想手动从代码复制方法签名, 只能手写一个工具了
这个段代码以满足公司要求来编写, 没有多余精力去优化代码了, 其中用到了扩展方法导致很多地方不合理. 但是总归不用手动写文档了, 避免了很多无意义的工作.
// First: Install-Package NPOI using NPOI.XWPF.UserModel; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; public static class InterfaceExportHelper { static BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Static; public static int spaceIncreaseCountOfEachLevel = 4; public static string CSharpClassColor = "2B93AF"; public static string CSharpKeywordColor = "0000FF"; public static bool IsShowParameterDefaultValue = false; public static void SeparateInterfaceToDocument(string assemblyPath, string outputDocPath) { var assembly = Assembly.LoadFrom(assemblyPath); var types = assembly.GetTypes().OrderBy(t => t.Name); XWPFDocument doc = new XWPFDocument(); var spaceCount = 0; foreach (var type in types) { if (type.IsClass) { var p = DealClass(type, doc, spaceCount); if (p != null) { p.AppendCarriageReturn(); } } else if (type.IsEnum) { var p = DealEnum(type, doc, spaceCount); if (p != null) { p.AppendCarriageReturn(); } } else if (type.IsValueType) { var p = DealStruct(type, doc, spaceCount); if (p != null) { p.AppendCarriageReturn(); } } } var fs = new FileStream(outputDocPath, FileMode.Create); doc.Write(fs); fs.Close(); } static XWPFParagraph DealClass(Type type, XWPFDocument doc, int spaceCount) { if (!type.IsPublic) { return null; } //if (type.IsNestedPrivate) //{ // return null; //} if (type.Name.StartsWith("<")) { return null; } var p = doc.CreateParagraph(); p.AppendSpaces(spaceCount); if (type.IsPublic) { p.AppendKeywordCSharp("public"); } else { p.AppendKeywordCSharp("internal"); } p.AppendSpaces(); if (type.IsAbstract && type.IsSealed) { p.AppendKeywordCSharp("static"); p.AppendSpaces(); } else if (type.IsSealed) { p.AppendKeywordCSharp("sealed"); p.AppendSpaces(); } else if (type.IsAbstract) { p.AppendKeywordCSharp("abstract"); p.AppendSpaces(); } p.AppendKeywordCSharp("class"); p.AppendSpaces(); p.AppendClassCSharp(type.Name); bool hasBaseType = false; if (type.BaseType != null && type.BaseType != typeof(object)) { hasBaseType = true; p.AppendText(" : "); DispalyType(type.BaseType, p); } bool isFisrtInterface = true; foreach (var interfaceType in type.GetInterfaces()) { if (!hasBaseType) { p.AppendText(" : "); } if (!isFisrtInterface || hasBaseType) { p.AppendText(", "); } DispalyType(interfaceType, p); isFisrtInterface = false; } p.AppendCarriageReturn(); var tempSpaceCount = spaceCount;// - spaceIncreaseCountOfEachLevel > 0 ? spaceCount - spaceIncreaseCountOfEachLevel : 0; p.AppendSpaces(tempSpaceCount); p.AppendText("{"); p.AppendCarriageReturn(); bool hasAddedSomething = false; foreach (var filedInfo in type.GetFields(bindingFlags)) { if (!filedInfo.IsPublic && !filedInfo.IsFamily) { continue; } DealFieldInfo(filedInfo, p, spaceCount + spaceIncreaseCountOfEachLevel); hasAddedSomething = true; } if (hasAddedSomething) p.AppendCarriageReturn(); hasAddedSomething = false; foreach (ConstructorInfo constructorInfo in type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) { if (DealConstructor(constructorInfo, p, spaceCount + spaceIncreaseCountOfEachLevel) == true) { hasAddedSomething = true; } } if (hasAddedSomething) p.AppendCarriageReturn(); hasAddedSomething = false; foreach (var propertyInfo in type.GetProperties(bindingFlags)) { if (DealProperty(propertyInfo, p, spaceCount + spaceIncreaseCountOfEachLevel) == true) { hasAddedSomething = true; } } if (hasAddedSomething) p.AppendCarriageReturn(); hasAddedSomething = false; foreach (MethodInfo method in type.GetMethods(bindingFlags)) { if (DealMethod(method, p, spaceCount + spaceIncreaseCountOfEachLevel) == true) { hasAddedSomething = true; } } if (hasAddedSomething) p.AppendCarriageReturn(); p.RemoveRun(p.Runs.Count - 1); p.AppendSpaces(tempSpaceCount); p.AppendText("}"); return p; } static bool DealFieldInfo(FieldInfo fieldInfo, XWPFParagraph p, int spaceCount) { if (!(fieldInfo.IsPublic || fieldInfo.IsFamily)) { return false; } //if (fieldInfo.IsPrivate) //{ // return false; //} p.AppendSpaces(spaceCount); var eventFieldList = new List<FieldInfo>(); if (fieldInfo.IsPublic) { p.AppendKeywordCSharp("public"); } else if (fieldInfo.IsFamily) { p.AppendKeywordCSharp("protected"); } else if (fieldInfo.IsAssembly) { p.AppendKeywordCSharp("internal"); } else if (fieldInfo.IsFamilyOrAssembly) { p.AppendKeywordCSharp("internal protected"); } p.AppendSpaces(); if (fieldInfo.IsInitOnly) { p.AppendKeywordCSharp("readonly"); p.AppendSpaces(); } if (fieldInfo.IsStatic) { p.AppendKeywordCSharp("static"); p.AppendSpaces(); } DispalyType(fieldInfo.FieldType, p); p.AppendSpaces(); p.AppendText(fieldInfo.Name); p.AppendText(";"); p.AppendCarriageReturn(); return true; } static bool DealProperty(PropertyInfo propertyInfo, XWPFParagraph p, int spaceCount) { AccessorModifier? getterAccessorModifier = null, setterAccessorModifier = null; if (propertyInfo.CanRead) { getterAccessorModifier = GetAccessorModifier(propertyInfo.GetMethod); } if (propertyInfo.CanWrite) { setterAccessorModifier = GetAccessorModifier(propertyInfo.SetMethod); } var mainAccessorModifier = GetHighAccessorModifier(getterAccessorModifier, setterAccessorModifier); if (!(mainAccessorModifier == AccessorModifier.Public || mainAccessorModifier == AccessorModifier.Protected)) { return false; } p.AppendSpaces(spaceCount); p.AppendKeywordCSharp(AccessorModifierToString(mainAccessorModifier)); p.AppendSpaces(); DispalyType(propertyInfo.PropertyType, p); p.AppendSpaces(); p.AppendText(propertyInfo.Name); p.AppendSpaces(); p.AppendText("{"); if (propertyInfo.CanRead && getterAccessorModifier >= AccessorModifier.Protected) { p.AppendSpaces(); if (getterAccessorModifier != mainAccessorModifier) { p.AppendKeywordCSharp(AccessorModifierToString(getterAccessorModifier.Value)); p.AppendSpaces(); } p.AppendKeywordCSharp("get;"); } if (propertyInfo.CanWrite && setterAccessorModifier >= AccessorModifier.Protected) { p.AppendSpaces(); if (setterAccessorModifier != mainAccessorModifier) { p.AppendKeywordCSharp(AccessorModifierToString(setterAccessorModifier.Value)); p.AppendSpaces(); } p.AppendKeywordCSharp("set;"); } p.AppendSpaces(); p.AppendText("}"); p.AppendCarriageReturn(); return true; } static bool DealConstructor(ConstructorInfo constructorInfo, XWPFParagraph p, int spaceCount) { if (!(constructorInfo.IsPublic || constructorInfo.IsFamily)) { return false; } //if (constructorInfo.IsPrivate) //{ // return false; //} p.AppendSpaces(spaceCount); if (constructorInfo.IsPublic) { p.AppendKeywordCSharp("public"); } else if (constructorInfo.IsFamily) { p.AppendKeywordCSharp("protected"); } else if (constructorInfo.IsAssembly) { p.AppendKeywordCSharp("internal"); } else if (constructorInfo.IsFamilyOrAssembly) { p.AppendKeywordCSharp("internal protected"); } p.AppendSpaces(); if (constructorInfo.IsAbstract) { p.AppendKeywordCSharp("abstract"); p.AppendSpaces(); } p.AppendClassCSharp(constructorInfo.DeclaringType.Name); p.AppendText("("); bool isFirst = true; foreach (var parameterInfo in constructorInfo.GetParameters()) { if (!isFirst) { p.AppendText(", "); } DispalyType(parameterInfo.ParameterType, p); p.AppendSpaces(); p.AppendText(parameterInfo.Name); isFirst = false; } p.AppendText(");"); p.AppendCarriageReturn(); return true; } static bool DealMethod(MethodInfo method, XWPFParagraph p, int spaceCount) { if (!(method.IsPublic || method.IsFamily)) { return false; } //if (method.IsPrivate) //{ // return false; //} if (method.Name.StartsWith("get_") || method.Name.StartsWith("set_")) { return false; } p.AppendSpaces(spaceCount); if (method.Name == "Finalize") { p.AppendText("~"); p.AppendClassCSharp(method.DeclaringType.Name); p.AppendText("();"); p.AppendCarriageReturn(); return true; } if (method.IsPublic) { p.AppendKeywordCSharp("public"); } else if (method.IsFamily) { p.AppendKeywordCSharp("protected"); } else if (method.IsAssembly) { p.AppendKeywordCSharp("internal"); } else if (method.IsFamilyAndAssembly) { p.AppendKeywordCSharp("internal protected"); } p.AppendSpaces(); if (method.IsStatic) { p.AppendKeywordCSharp("static"); p.AppendSpaces(); } // a. override parent class method, // b.implement a interface // both have Final & virtual keywords bool isSealed = false; if (method.IsFinal && method != method.GetBaseDefinition()) { p.AppendKeywordCSharp("sealed"); p.AppendSpaces(); isSealed = true; } if (method.IsVirtual) { if (method != method.GetBaseDefinition()) { p.AppendKeywordCSharp("override"); } else { if (!method.IsFinal) { p.AppendKeywordCSharp("virtual"); } } p.AppendSpaces(); } if (method.IsAbstract) { p.AppendKeywordCSharp("abstract"); p.AppendSpaces(); } DispalyType(method.ReturnType, p); p.AppendSpaces(); p.AppendText(method.Name); p.AppendText("("); bool isFirst = true; foreach (var parameterInfo in method.GetParameters()) { if (!isFirst) { p.AppendText(", "); } if (parameterInfo.IsOut) { p.AppendKeywordCSharp("out"); p.AppendSpaces(); } else if (parameterInfo.IsIn) { p.AppendKeywordCSharp("in"); p.AppendSpaces(); } else if (parameterInfo.ParameterType.IsByRef) { p.AppendKeywordCSharp("ref"); p.AppendSpaces(); } DispalyType(parameterInfo.ParameterType, p); p.AppendSpaces(); p.AppendText(parameterInfo.Name); if (IsShowParameterDefaultValue && parameterInfo.HasDefaultValue) { p.AppendSpaces(); p.AppendText("="); p.AppendSpaces(); if (parameterInfo.DefaultValue == null) { p.AppendText("null"); } else if (parameterInfo.ParameterType == typeof(string)) { p.AppendText("\""); p.AppendText(parameterInfo.DefaultValue.ToString()); p.AppendText("\""); } else if (parameterInfo.ParameterType == typeof(char)) { p.AppendText("'"); p.AppendText(parameterInfo.DefaultValue.ToString()); p.AppendText("'"); } else if (parameterInfo.ParameterType.IsEnum) { DispalyType(parameterInfo.ParameterType, p); p.AppendText("."); p.AppendText(parameterInfo.DefaultValue.ToString()); } else { p.AppendText(parameterInfo.DefaultValue.ToString()); } } isFirst = false; } p.AppendText(");"); p.AppendCarriageReturn(); return true; } static void DispalyType(Type type, XWPFParagraph p) { // Nullable<> need change to like int? if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { DispalyType(type.GetGenericArguments()[0], p); p.AppendText("?"); return; } var typeName = type.Name; if (typeName.Contains("`")) { typeName = typeName.Substring(0, typeName.LastIndexOf('`')); } typeName = ChangeToNormalName(typeName); p.AppendClassCSharp(typeName); if (type.IsGenericType) { p.AppendText("<"); bool isFisrt = true; foreach (var genericArgumentType in type.GetGenericArguments()) { if (!isFisrt) { p.AppendText(", "); } DispalyType(genericArgumentType, p); isFisrt = false; } p.AppendText(">"); } } static string ChangeToNormalName(string typeName) { typeName = typeName.TrimEnd('&'); switch (typeName) { case "Void": return "void"; case "Object": return "object"; case "String": return "string"; case "Boolean": return "bool"; case "Byte": return "byte"; case "Char": return "char"; case "Int16": return "short"; case "Int32": return "int"; case "Int64": return "long"; case "Single": return "float"; case "Double": return "double"; default: return typeName; } } static XWPFParagraph DealEnum(Type type, XWPFDocument doc, int spaceCount) { if (type.IsNestedPrivate) { return null; } var p = doc.CreateParagraph(); if (type.GetCustomAttribute<FlagsAttribute>() != null) { p.AppendSpaces(spaceCount); p.AppendText("["); p.AppendKeywordCSharp("Flags"); p.AppendText("]"); p.AppendCarriageReturn(); } p.AppendSpaces(spaceCount); if (type.IsPublic) { p.AppendKeywordCSharp("public"); } else if (type.IsNestedAssembly) { p.AppendKeywordCSharp("internal"); } p.AppendSpaces(); p.AppendKeywordCSharp("enum"); p.AppendSpaces(); p.AppendClassCSharp(type.Name); var tempSpaceCount = spaceCount;// - spaceIncreaseCountOfEachLevel > 0 ? spaceCount - spaceIncreaseCountOfEachLevel : 0; p.AppendCarriageReturn(); p.AppendSpaces(tempSpaceCount); p.AppendText("{"); p.AppendCarriageReturn(); foreach (var enumName in type.GetEnumNames()) { p.AppendSpaces(spaceCount + spaceIncreaseCountOfEachLevel); p.AppendText(enumName); p.AppendText(","); p.AppendCarriageReturn(); } p.AppendSpaces(tempSpaceCount); p.AppendText("}"); return p; } static XWPFParagraph DealStruct(Type type, XWPFDocument doc, int spaceCount) { if (!type.IsPublic) { return null; } //if (type.IsNestedPrivate) //{ // return null; //} if (type.Name.StartsWith("<")) { return null; } var p = doc.CreateParagraph(); p.AppendSpaces(spaceCount); if (type.IsPublic) { p.AppendKeywordCSharp("public"); } else { p.AppendKeywordCSharp("internal"); } p.AppendSpaces(); p.AppendKeywordCSharp("struct"); p.AppendSpaces(); p.AppendClassCSharp(type.Name); bool isFisrtInterface = true; foreach (var interfaceType in type.GetInterfaces()) { if (isFisrtInterface) { p.AppendText(" : "); } else { p.AppendText(", "); } DispalyType(interfaceType, p); isFisrtInterface = false; } p.AppendCarriageReturn(); var tempSpaceCount = spaceCount;// - spaceIncreaseCountOfEachLevel > 0 ? spaceCount - spaceIncreaseCountOfEachLevel : 0; p.AppendSpaces(tempSpaceCount); p.AppendText("{"); p.AppendCarriageReturn(); bool hasAddedSomething = false; foreach (var filedInfo in type.GetFields(bindingFlags)) { if (!filedInfo.IsPublic && !filedInfo.IsFamily) { continue; } DealFieldInfo(filedInfo, p, spaceCount + spaceIncreaseCountOfEachLevel); hasAddedSomething = true; } if (hasAddedSomething) p.AppendCarriageReturn(); hasAddedSomething = false; foreach (ConstructorInfo constructorInfo in type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) { if (DealConstructor(constructorInfo, p, spaceCount + spaceIncreaseCountOfEachLevel) == true) { hasAddedSomething = true; } } if (hasAddedSomething) p.AppendCarriageReturn(); hasAddedSomething = false; foreach (var propertyInfo in type.GetProperties(bindingFlags)) { if (DealProperty(propertyInfo, p, spaceCount + spaceIncreaseCountOfEachLevel) == true) { hasAddedSomething = true; } } if (hasAddedSomething) p.AppendCarriageReturn(); hasAddedSomething = false; foreach (MethodInfo method in type.GetMethods(bindingFlags)) { if (DealMethod(method, p, spaceCount + spaceIncreaseCountOfEachLevel) == true) { hasAddedSomething = true; } } if (hasAddedSomething) p.AppendCarriageReturn(); p.RemoveRun(p.Runs.Count - 1); p.AppendSpaces(tempSpaceCount); p.AppendText("}"); return p; } static AccessorModifier GetAccessorModifier(MethodInfo method) { if (method.IsPublic) { return AccessorModifier.Public; } if (method.IsAssembly) { return AccessorModifier.Internal; } if (method.IsFamily) { return AccessorModifier.Protected; } if (method.IsFamilyOrAssembly) { return AccessorModifier.InternalProtected; } return AccessorModifier.Private; } static AccessorModifier GetHighAccessorModifier(AccessorModifier? a, AccessorModifier? b) { // a or b at least have one value if (a.HasValue && b.HasValue) { return (AccessorModifier)Math.Max((int)a.Value, (int)b.Value); } else if (a.HasValue == false) { return b.Value; } else { return a.Value; } } static string AccessorModifierToString(AccessorModifier accessorModifier) { switch (accessorModifier) { case AccessorModifier.Public: return "public"; case AccessorModifier.Internal: return "internal"; case AccessorModifier.Protected: return "protected"; break; case AccessorModifier.InternalProtected: return "internal protected"; case AccessorModifier.Private: return "private"; default: return ""; } } private enum AccessorModifier { Public = 100, Protected = 95, Internal = 90, InternalProtected = 80, Private = 70, } } internal static class NopiExtension { internal static XWPFRun AppendText(this XWPFParagraph paragraph, string text, string color = "000000") { XWPFRun run = paragraph.CreateRun(); run.SetText(text); run.SetColor(color); return run; } internal static XWPFRun AppendSpaces(this XWPFParagraph paragraph, int spaceCount = 1) { return paragraph.AppendText(new string(' ', spaceCount)); } internal static XWPFRun AppendKeywordCSharp(this XWPFParagraph paragraph, string text) { return paragraph.AppendText(text, InterfaceExportHelper.CSharpKeywordColor); } internal static XWPFRun AppendClassCSharp(this XWPFParagraph paragraph, string text) { return paragraph.AppendText(text, InterfaceExportHelper.CSharpClassColor); } internal static XWPFRun AppendCarriageReturn(this XWPFParagraph paragraph) { var run = paragraph.CreateRun(); run.AddCarriageReturn(); return run; } }
用法
var assemblyPath = @"D:\Client\bin\Debug\Client.dll"; var outputDocPath = @"D:\Client.docx"; InterfaceExportHelper.IsShowParameterDefaultValue = false; InterfaceExportHelper.SeparateInterfaceToDocument(assemblyPath, outputDocPath);