繁体   English   中英

如何识别C#项目中未使用的类

[英]How to identify unused classes in C# project

我有一个包含不同项目的C#大解决方案。 它还包含一个带有 Main static 方法的批次。 我必须从这个方法开始识别并删除所有不能使用的类。 哪种方法最好? 我正在使用 Microsoft Visual Studio Professional 2015

谢谢!

没有工具可以完全做到这一点,因为

  • System.Reflection和System.CodeDom存在 - 是否可以动态编译和执行C#代码片段?
  • 可以在运行时生成新的C#代码,该代码使用其他未使用的类。
  • 没有工具可以预测新的C#代码是什么(除了编写代码的人类)
  • 依赖注入库(在后台使用System.Reflection)可以调用“未使用”类。 这种情况经常发生在MVC Controller类中。
  • Razor Views可以使用类。 这些不是默认编译的。 相反,如果缺少类,它们将在运行时崩溃。

假设没有人使用System.Reflection,你可以手动完成。

每个班级:

  • 在Visual Studio中选择它,右键单击“查找所有引用”
  • 如果没有找到,请将课程注释掉/ * * /
  • 重建所有( 包括Razor视图 )。 如果未发现错误,则该类未使用。

您可以通过右键单击并检查引用来尝试。 方法,类和属性也是可能的。 但它不会显示表示层中引用的类,方法或属性

可能值得下载并开始resharper的免费试用。 通过使用“解决方案范围分析”功能,您将能够快速找到解决方案中未使用的代码。 此外,还有许多其他很酷的功能!

根据我对该问题的理解,您选择查找无法通过批处理中的main方法访问的类。

根据解决方案的布局,可以通过添加引用和使用using关键字从main方法项目到达所有类。可以通过添加dll然后导入命名空间来完成对其他解决方案项目的外部引用。

如果您想要执行清理并从解决方案中删除未使用的类,则需要仔细检查类的直接和间接引用。

认为以上回答你的问题,如果你满意,请标记为答案。

下面的代码可以与FxCopCmd.exe命令行可执行文件一起使用。 FxCopCmd.exe是默认Visual Studio安装的一部分。 首先,您必须设置一个新项目并将代码编译为 DLL。

在 Visual Studio 中,创建一个新的Class Library项目。 选择一个名称,如FxCopDemo 它必须以.NET Framework 4或更新版本为目标。 在编译期间,将显示有关构建的警告消息。 可以通过将Properties > Build > Platform target更改为x86来修复警告消息。

在项目中,添加对以下内容的引用:

  • FxCopSdk.dll
  • Microsoft.Cci.dll

根据您的Visual Studio版本,DLL 位于如下文件夹中:

  • C:\Program Files (x86)\Microsoft Visual Studio 11.0\Team Tools\Static Analysis Tools\FxCop\
  • C:\Program Files\Microsoft Visual Studio 9.0\Team Tools\Static Analysis Tools\FxCop\Rules\

确保执行Debug build ,以便生成程序数据库 (PDB) 文件。 PDB文件将为FxCop提供它需要的调试信息 output 来自源代码的行号。

将此 XML 复制并粘贴到名为UnusedCodeXml.xml的文件中,并将其作为现有项添加到项目中。

<?xml version="1.0" encoding="utf-8"?>
<Rules FriendlyName="">

<Rule TypeName="UnusedClassesRule" Category="UnusedCode" CheckId="UC0001">
    <Name>A class or class cluster is not referenced by the main code.</Name>
    <Description>
A class is included as part of an executable, or class library, but that code is not used.
    </Description>
    <Url></Url>
    <Resolution Name="Orphan">The class '{0}' is not referenced by any other classes.</Resolution>
    <Resolution Name="Cluster">The class '{0}' is only referenced by classes in an isolated cluster.</Resolution>
    <MessageLevel Certainty="95">Warning</MessageLevel>
    <Email></Email>
    <FixCategories>NonBreaking</FixCategories>
    <Owner></Owner>
</Rule>

</Rules>

重要提示:将 XML 规则文件添加到项目后。 然后右键单击该项目,转到Properties ,然后将Build Action更改为Embedded Resource

注意: C#代码有点太长,不能放在这篇文章中,所以代码贴在下面。 将我的其他答案中的C#代码复制并粘贴到名为UnusedClassesRule.cs的文件中。 将文件添加到项目中。 编译项目应该创建一个名为C:\Projects\FxCopDemo\bin\Debug\FxCopDemo.dll的文件。

现在打开Command Prompt window 和 go 到包含FxCopCmd.exe可执行文件的目录。 FxCopCmd.exe似乎对 arguments 的传入顺序有点挑剔。似乎/d:在使用时必须最后指定。

案例 1) 分析单个可执行文件,即MyProgram.exe 命令行是:

FxCopCmd "/f:C:\Projects\MyProgram\bin\Debug\MyProgram.exe" /r:C:\Projects\FxCopDemo\bin\Debug\FxCopDemo.dll /out:c:\temp\refs.csv /q /c

  • /f:是要分析的exe的路径
  • /r:是规则的路径 dll
  • /out:可选。 将 output 保存到指定文件
  • /q表示安静
  • /c可选。 将结果输出到控制台。
  • /v可选。 冗长。 如果指定,每个 class 的所有参考文献列表将包含在 output 中。

情况 2) 您有一个编译为DLLEXE的项目。 在这种情况下,将文件MyProgram.exeMyProgram.Core.dll和 PBD 文件复制到单独的子文件夹中,例如\bin\Debug\fxcop\ /f:参数将指向子文件夹。

FxCopCmd /f:C:\Projects\MyProgram\bin\Debug\fxcop\ /r:C:\Projects\FxCopDemo\bin\Debug\FxCopDemo.dll /out:c:\temp\refs.csv /q /c /d:"C:\Projects\MyProgram\bin\Debug\"

在这种情况下,需要一个额外的/d:参数。 它指向主Debug文件夹,该文件夹可能包含您不想包含在分析中但FxCop需要的第三方 DLL。

这是示例 output 的屏幕截图。它输出任何未被其他类引用的类,并检测与代码 rest 断开连接的类。

示例输出

最后,以下文档有助于解决FxCop的任何问题:

https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.466.282&rep=rep1&type=pdf

这是UnusedClassesRule.cs class 的代码:

using Microsoft.FxCop.Sdk;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FxCopDemo {

[Flags]
public enum CodeLocation {
    StaticInitializer           = 4096,
    ConstructorParameter        = 1,
    ConstructorBody             = 2,
    //---
    MethodParameter             = 4,
    MethodBody                  = 8,
    MethodReturnType            = 16,
    StaticMethodBody            = 2048,
    MethodDef                   = 8192,
    ClassDef                    = 16384,
    //---
    PropertyReturnType          = 32,
    PropertyGetterBody          = 64,
    PropertySetterBody          = 128,
    //---
    EventDelegate               = 256,
    //---
    MemberVariable              = 512,
    StaticVariable              = 1024
}

public class RefItem {

    ///<summary>All the locations in the source code where the full name is referenced.</summary>
    public CodeLocation Locations = (CodeLocation) 0;

    ///<summary>The full name of the class, struct, or enum.</summary>
    public readonly String FullName;

    ///<summary>The total number of times the FullName is referenced by the ClassTypes.</summary>
    public int Count;

    public RefItem(String fullName) {
        this.FullName = fullName;
    }
}

// for each custom class, maintain a collection of all types that the class references
public class Class2 : IComparable<Class2> {

    ///<summary>List of other class types that have a direct reference to this class. This list is populated at the very end.</summary>
    public List<Class2> Nbors = new List<Class2>();

    ///<summary>The full name of this class, struct, or enum type.</summary>
    public readonly String FullName;

    ///<summary>If this class is a nested class, the DeclaringType is the full name of the immediate host.</summary>
    public readonly String DeclaringType;

    ///<summary>The full name of the immediate parent.</summary>
    public readonly String BaseType;

    ///<summary>True if this class contains the Main(...) entry point method.</summary>
    public readonly bool IsEntryPoint;

    ///<summary>The type of node, typically Class, Interface, DelegateNode, EnumNode. Used for reporting purposes.</summary>
    public readonly NodeType NodeType;

    private Dictionary<String, RefItem> dict = new Dictionary<String, RefItem>();

    public Class2(String fullName, bool isEntryPoint, String declaringType, String baseType, NodeType nodeType) {
        this.FullName = fullName;
        this.IsEntryPoint = isEntryPoint;
        this.DeclaringType = declaringType;
        this.BaseType = baseType;
        this.NodeType = nodeType;
    }
    
    ///<summary>
    ///Adds a reference to a class that this class directly references.
    ///<param name="fullName">The full name of the class that this class references.</param>
    ///<param name="loc">The location in the code where the reference occurs. CodeLocation is a [Flags] enum, so multiple locations can exist.</param>
    ///</summary>
    public void Add(String fullName, CodeLocation loc) {
        // variables passed with 'ref' or 'out' as method parameters have an '@' as the last character of the fullName
        fullName = fullName.TrimEnd('@');
        lock(dict) {
            RefItem ri = null;
            if (!dict.TryGetValue(fullName, out ri)) {
                ri = new RefItem(fullName);
                dict[fullName] = ri;
            }
            ri.Locations = (ri.Locations | loc);
            ri.Count++;
        }
    }

    public RefItem GetRef(String fullName) {
        RefItem ri = null;
        dict.TryGetValue(fullName, out ri);
        return ri;
    }

    public List<RefItem> GetRefs() {
        return dict.Values.OrderBy(r => r.FullName).ToList();
    }

    public override String ToString() {
        return FullName;
    }

    public int CompareTo(Class2 other) {
        return String.Compare(this.FullName, other.FullName);
    }
}


/*
For each type node, detect all other types it references.

Then for each node, determine the other nodes that point to it.

Report:
1. Any nodes that aren't pointed to, and
2. Any groups of nodes that are not connected to the main entry point.

Limitations:
1. Seems like when an enum is cast to a int value, the original enum type is not recoverable
   e.g: int x = (int) MyEnum.SomeValue; // seems like the compiler replaces the MyEnum.SomeValue reference with the literal value
   This means that some enums may be reported as unused, but they are actually used.

2. Unreachable code is reported as unused. Not sure how to access unreachable code using the FxCop api.
3. Does not account for reflection that uses hard coded string names.

The following are too granular and are not reported in this rule:
1. Methods (instance or static) that are never called.
2. Variables (properties or fields, instance or static) in classes not used.
3. Variables in method arguments not used.
*/
public class UnusedClassesRule : BaseIntrospectionRule {

    private static Dictionary<String, Class2> dictTypes = new Dictionary<String, Class2>();

    // The multi-threads in FxCop make it hard to debug. The messages are grouped by thread when debugging of this rule is needed.
    private static Dictionary<int, StringBuilder> dictOutput = new Dictionary<int, StringBuilder>();

    private Class2 currentC2 = null;
    private CodeLocation currentLoc = (CodeLocation) 0;
    private Member currentMember = null;
    private bool writeCheckMemberError = true;
    private bool writeVisitExpressionError = true;

    public UnusedClassesRule() : base("UnusedClassesRule", "FxCopDemo.UnusedCodeXml", typeof(UnusedClassesRule).Assembly) {
    }

    public override TargetVisibilities TargetVisibility {
        get {
            return TargetVisibilities.All;
        }
    }

    // Note: BeforeAnalysis() and AfterAnalysis() are called multiple times, one for each rule instance.
    // According to a forum post on MSDN, one rule instance is created for each CPU core.
    //public override void BeforeAnalysis() {
        //base.BeforeAnalysis();
        //MethodCollection list = CallGraph.CallersFor((Method) null);
    //}

    public override void AfterAnalysis() {
        base.AfterAnalysis();
        lock(dictTypes) {
            if (dictTypes.Count == 0)
                return;

            try {
                FinalAnalysis();
            } catch (Exception ex) {
                writeCheckMemberError = false;
                Write("Error: " + ex.GetType().FullName + ": " + ex.Message, null, true);
                Write(ex.StackTrace, null, true);
            }
            dictTypes.Clear();      
        }
    }

    public override ProblemCollection Check(Member member) {
        try {
            CheckInternal(member);
        } catch (Exception ex) {
            if (writeCheckMemberError) {
                writeCheckMemberError = false;
                Write("Error: " + ex.GetType().FullName + ": " + ex.Message);
                Write(ex.StackTrace);
            }
        }
        return this.Problems;
    }

    private ProblemCollection CheckInternal(Member member) {
        Class2 ct = currentC2;
        
        AttributeNodeCollection attribs = member.Attributes;
        for (int i = 0; i < attribs.Count; i++) {
            AttributeNode a = attribs[i];
            Add(ct, a.Type, CodeLocation.MethodDef);
        }

        if (member is Method) {
            currentMember = member;

            var m = (Method) member;
            if (m.ReturnType != FrameworkTypes.Void)
                Add(ct, m.ReturnType, CodeLocation.MethodReturnType);

            if (m.NodeType == NodeType.InstanceInitializer)
                currentLoc = CodeLocation.ConstructorBody;
            else if (m.NodeType == NodeType.StaticInitializer)
                currentLoc = CodeLocation.StaticInitializer;
            else
                currentLoc = (m.IsStatic ? CodeLocation.StaticMethodBody : CodeLocation.MethodBody);

            for (int i = 0; i < m.Parameters.Count; i++) {
                Parameter p = m.Parameters[i];
                TypeNode ty = p.Type;
                Add(ct, ty, CodeLocation.MethodParameter);
            }

            // the DeclaringType is needed when an extension method is called
            Add(ct, m.DeclaringType, currentLoc);

            VisitMethod(m);
            //VisitBlock(m.Body);
            //VisitStatements(m.Body.Statements);
        }
        else if (member is PropertyNode) {  
            var pn = (PropertyNode) member;
            if (pn.Setter != null) {
                currentMember = member;
                currentLoc = CodeLocation.PropertySetterBody;
                VisitStatements(pn.Setter.Body.Statements);
            }
            if (pn.Getter != null) {
                Add(ct, pn.Getter.ReturnType, CodeLocation.PropertyReturnType);
                currentMember = member;
                currentLoc = CodeLocation.PropertyGetterBody;
                VisitStatements(pn.Getter.Body.Statements);
            }
        }
        else if (member is Field) {
            var f = (Field) member;
            var loc = (f.IsStatic ? CodeLocation.StaticVariable : CodeLocation.MemberVariable);
            Add(ct, f.Type, loc);
        }
        else if (member is EventNode) {
            var evt = (EventNode) member;
            Add(ct, evt.HandlerType, CodeLocation.EventDelegate);
        }
        else {
            Write("UNKNOWN CHECK MEMBER: " + member.GetType().FullName + "   full name: " + member.FullName);
        }

        return this.Problems;
    }

    private static void Add(Class2 ct, TypeNode ty, CodeLocation loc) {
        // Given a TypeNode, it may have to be drilled down into determine it's actual type
        // It's also possible that a single TypeNode results in multiple references. For example,
        // public class MyList<T> { ... }
        // public static void Foo<U>(MyList<U> list) where U : Bar {}
        // which should result in adding namespace.MyList, and namespace.Bar

        Stack<TypeNode> stack = new Stack<TypeNode>();
        stack.Push(ty);
        while (stack.Count > 0) {
            ty = stack.Pop();
            if (ty == null)
                continue;

            if (ty.IsGeneric) {
                // otherwise Nullable`1 shows up in the list of references
                if (ty.Template != null && ty.Template != FrameworkTypes.GenericNullable)
                    ct.Add(ty.Template.FullName, loc);

                if (ty.TemplateArguments != null) {
                    foreach (TypeNode ta in ty.TemplateArguments)
                        stack.Push(ta);
                }
                if (ty.TemplateParameters != null) {
                    foreach (TypeNode tp in ty.TemplateParameters)
                        stack.Push(tp);
                }
            }
            else if (ty.IsTemplateParameter) {
                // this is the case when a constraint is placed on a generic type, e.g. public static void Foo<T>(U input) where U : Bar { ... }
                // ty.FullName will be something like "parameter.T"
                // The BaseType == "Bar". If no constraint is specified, then BaseType == "System.Object".
                // The reason it's pushed on the stack is because it could be a constraint like where : U : MyList<Bar> { ... }
                stack.Push(ty.BaseType);
            }
            else if (ty is ArrayType) {
                ArrayType ty2 = (ArrayType) ty;
                TypeNode ty3 = ty2.ElementType;
                ct.Add(ty3.FullName, loc);
            }
            else {
                ct.Add(ty.FullName, loc);
            }
        }
    }

    public override void VisitExpression(Expression exp) {
        try {
            VisitExpressionInternal(exp);
        } catch (Exception ex) {
            if (writeVisitExpressionError) {
                writeVisitExpressionError = false; // only record the first error
                Write("Error: " + ex.GetType().FullName + ": " + ex.Message, null, true);
                Write(ex.StackTrace);
            }
        }
    }

    private void VisitExpressionInternal(Expression exp) {
        if (exp == null)
            return;

        Stack<Expression> stack = new Stack<Expression>();
        stack.Push(exp);

        while (stack.Count > 0) {
            exp = stack.Pop();
            if (exp == null)
                continue;

            if (exp is NaryExpression) {
                // this handles Construct, ConstructArray, MethodCall, Indexer
                var ne = (NaryExpression) exp;
                foreach (Expression e in ne.Operands) {
                    stack.Push(e); // the actual arguments
                }
            }

            TypeNode ty = exp.Type;
            bool alert = false;
            if (exp is Variable || exp is Local) { // Note: Local extends Variable
                Add(currentC2, ty, currentLoc);
                //Write("VARIABLE NAME: " + ((Variable) exp).Name.Name + "  " + exp);
            }
            else if (exp is Literal) {
                var lit = (Literal) exp;
                Add(currentC2, ty, currentLoc);
                Object val = lit.Value;
                if (val is TypeNode)
                    Add(currentC2, (TypeNode) val, currentLoc);
                else if (val != null) {
                    // val == null when there is an assignment like, MyClass c = null;
                    Type ty2 = val.GetType();
                    bool isKnown = (ty2.IsPrimitive || ty2 == typeof(String));
                    if (isKnown) {
                        currentC2.Add(ty2.FullName, currentLoc);
                    }
                    else {
                        Write("val.GetType(): " + ty2.FullName);
                        alert = true;
                    }
                }

                //if (val is EnumNode) {
                //  var en = (EnumNode) val;
                //  en.UnderlyingType; // this is the primitive type that the Enum extends, typically int32
                //}
            }
            else if (exp is MethodCall) {   
                var mc = (MethodCall) exp;
                stack.Push(mc.Callee);
            }
            else if (exp is Construct) {
                Add(currentC2, ty, currentLoc);
                var c = (Construct) exp;
                stack.Push(c.Constructor); // c.Constuctor is MemberBinding
            }
            else if (exp is Indexer) {
                var ix = (Indexer) exp;
                Add(currentC2, ix.ElementType, currentLoc); 
                stack.Push(ix.Object);
            }
            else if (exp is AddressDereference) {
                var ad = (AddressDereference) exp; // nullable type
                TypeNode ty2 = ad.Type;
                Add(currentC2, ty2, currentLoc);
                stack.Push(ad.Address);
            }
            else if (exp is MemberBinding) {
                var mb = (MemberBinding) exp;
                stack.Push(mb.TargetObject);
                if (mb.BoundMember is Method) { // Note: InstanceInitializer is a Method
                    var m = (Method) mb.BoundMember;
                    Add(currentC2, m.DeclaringType, currentLoc);
                    CodeLocation loc = (mb.BoundMember is InstanceInitializer ? CodeLocation.ConstructorParameter : currentLoc);
                    for (int i = 0; i < m.Parameters.Count; i++) {
                        Parameter p = m.Parameters[i];
                        TypeNode ty2 = p.Type;
                        Add(currentC2, ty2, loc);
                    }
                }
                else if (mb.BoundMember is Field) {
                    // this occurs for a class level static variable
                    var f = (Field) mb.BoundMember;
                    var loc = (f.IsStatic ? CodeLocation.StaticVariable : CodeLocation.MemberVariable);
                    Add(currentC2, f.Type, loc);
                    Add(currentC2, f.DeclaringType, loc);
                }
                //else if (mb.BoundMember is PropertyNode) { // can this happen?
                //}
                else {
                    alert = true;
                }
            }
            else if (exp is ConstructArray) {
                var ca = (ConstructArray) exp;
                Add(currentC2, ca.ElementType, currentLoc);
            }
            else if (exp is UnaryExpression) {
                // e.g. a method call
                var ue = (UnaryExpression) exp;
                stack.Push(ue.Operand);
            }
            else if (exp is BinaryExpression) {
                var be = (BinaryExpression) exp;
                stack.Push(be.Operand1);
                stack.Push(be.Operand2);
            }
            else if (exp is TernaryExpression) {
                var te = (TernaryExpression) exp;
                stack.Push(te.Operand1);
                stack.Push(te.Operand2);
                stack.Push(te.Operand3);
            }
            else if (exp is NamedArgument) {
                var na = (NamedArgument) exp;
                stack.Push(na.Value);
            }
            else {
                NodeType nt = exp.NodeType;
                bool ignore = (nt == NodeType.Pop || nt == NodeType.Dup);
                alert = !ignore;
            }

            if (alert)
                Write("x x " + currentC2.FullName + " " + currentMember.FullName + "   " + exp.ToString() + "   NodeType:" + exp.NodeType + "   Type:" + exp.Type);
        }
    }

    public override ProblemCollection Check(ModuleNode module) {
        //Write("Check_ModuleNode: " + module.Name);
        return this.Problems;
    }
    public override ProblemCollection Check(Parameter parameter) {
        //Write("Check_Parameter: " + parameter.Name);
        return this.Problems;
    }
    public override ProblemCollection Check(Resource resource) {
        //Write("Check_Resource: " + resource.Name);
        return this.Problems;
    }

    public override ProblemCollection Check(TypeNode type) {
        //Write("Check_TypeNode: " + type.FullName + "  declaring type: " + (type.DeclaringType == null ? "null" : type.DeclaringType.FullName) + " " + type.Interfaces);
        currentC2 = GetClass2(type.FullName, type);

        base.Check(type);

        AttributeNodeCollection attribs = type.Attributes;
        //Write("attributs.COunt: " + attribs.Count);
        for (int i = 0; i < attribs.Count; i++) {
            AttributeNode a = attribs[i];
            //Write("a.Type: " + a.Type);
            Add(currentC2, a.Type, CodeLocation.ClassDef);
        }

        InterfaceCollection iList = type.Interfaces;
        for (int i = 0; i < iList.Count; i++) {
            InterfaceNode n = iList[i];
            Add(currentC2, n, CodeLocation.ClassDef);
        }

        return this.Problems;
    }

    private static Class2 GetClass2(String fullName, TypeNode ty) {
        lock(dictTypes) {
            Class2 ct = null;
            if (!dictTypes.TryGetValue(fullName, out ct)) {
                AssemblyNode a = ty.ContainingAssembly();
                bool isEntryPoint = false;
                Method ep = a.EntryPoint;
                if (ep != null)
                    isEntryPoint = (String.Compare(fullName, ep.DeclaringType.FullName) == 0);

                TypeNode dt = ty.DeclaringType;
                TypeNode bt = ty.BaseType;
                
                String dt_ = (dt == null ? "" : dt.FullName);
                String bt_ = (bt == null ? "" : bt.FullName);
                //Write("type: " + ty.FullName + " nt:" + ty.NodeType + " dt:" + dt_ + " bt:" + bt_);
                ct = new Class2(fullName, isEntryPoint, dt_, bt_, ty.NodeType);
                dictTypes[fullName] = ct;
            }
            return ct;
        }
    }


    public override ProblemCollection Check(String namespaceName, TypeNodeCollection types) {
        //Write("Check_namespaceName_types: " + namespaceName + ", " + types.Count);
        return this.Problems;
    }

    protected override String GetLocalizedString(String name, params Object[] arguments) {
        String s = base.GetLocalizedString(name, arguments);
        //Write("GetLocalizedString: " + name);
        return s;
    }
    protected override Resolution GetNamedResolution(String name, params Object[] arguments) {
        Resolution r = base.GetNamedResolution(name, arguments);
        //Write("GetNamedResolution: " + name);
        return r;
    }

    private static void Write(String message, StreamWriter sw = null, bool console = false) {
        if (dictOutput == null) {
            if (console)
                Console.WriteLine(message);
            
            if (sw != null)
                sw.WriteLine(message);

            return;
        }

        lock(dictOutput) {
            int id = System.Threading.Thread.CurrentThread.ManagedThreadId;
            StringBuilder sb = null;
            if (!dictOutput.TryGetValue(id, out sb)) {
                sb = new StringBuilder();
                dictOutput[id] = sb;
            }
            sb.AppendLine(message);
        }
    }

    // chops off "Node" at the end, e.g: EnumNode > Node, DelegateNode > Delegate
    private static String GetName(NodeType nt) {
        String s = nt.ToString();
        if (s.EndsWith("Node"))
            s = s.Substring(0, s.Length - 4);
        return s;
    }

    private static void FinalAnalysis() {
        foreach (KeyValuePair<int, StringBuilder> pair in dictOutput) {
            Console.WriteLine();
            Console.WriteLine("Thread Id: " + pair.Key);
            Console.Write(pair.Value);
        }
        dictOutput = null;

        bool writeRefs = false;
        bool console = false;
        String outputFilename = null;
        StreamWriter sw2 = null;
        String[] args = Environment.GetCommandLineArgs(); // look at the args to determine what to output

        foreach (String arg in args) {
            if (arg.StartsWith("/out:")) {
                outputFilename = arg.Substring(5);
                String folder = Path.GetDirectoryName(outputFilename);
                if (!Directory.Exists(folder))
                    Directory.CreateDirectory(folder);

                System.Threading.Thread.Sleep(2000);
                sw2 = new StreamWriter(new FileStream(outputFilename, FileMode.Create, FileAccess.Write));
            }
            else if (arg == "/c" || arg == "/console")
                console = true;
            else if (arg == "/v" || arg == "/verbose")
                writeRefs = true;
        }


        Write("", sw2, console);

        List<Class2> ctList = dictTypes.Values.OrderBy(t => t.FullName).ToList();
        Hashtable htCluster = new Hashtable();
        Hashtable ht = new Hashtable();
        foreach (Class2 ct in ctList)
            ht[ct.FullName] = ct;

        foreach (Class2 ct in ctList) {
            if (ct.DeclaringType.Length == 0) {
                List<Class2> list = new List<Class2>();
                list.Add(ct);
                htCluster[ct] = list;
            }
        }

        foreach (Class2 ct in ctList) {
            if (ct.DeclaringType.Length == 0)
                continue;

            var ct2 = ct;
            while (ct2.DeclaringType.Length > 0)
                ct2 = (Class2) ht[ct2.DeclaringType];

            List<Class2> list = (List<Class2>) htCluster[ct2];
            ct2 = ct;
            while (ct2.DeclaringType.Length > 0) {
                list.Add(ct2);
                ct2 = (Class2) ht[ct2.DeclaringType];
            }
            htCluster[ct] = list;
        }

        if (writeRefs) {
            Write("ClassName,RefersTo,Locations", sw2, console);
            foreach (Class2 ct in ctList) {
                List<RefItem> refList = ct.GetRefs();
                foreach (RefItem ri in refList)
                    Write(ct.FullName + "," + ri.FullName + "," + ri.Locations.ToString().Replace(',', ' '), sw2, console);
            }
        }

        // if a nested class points to a parent class, don't count that as a reference
        // if the only reference to class A is the construtor body of class B and class B extends class A, then don't count that as a reference
        int n = ctList.Count;
        for (int i = 0; i < n; i++) {
            // check for other classes (ct2) that refer to ct
            for (int j = 0; j < n; j++) {
                if (i == j)
                    continue;

                Class2 ct = ctList[i];
                Class2 ct2 = ctList[j];

                bool isNested = false;
                String dt = ct2.DeclaringType;
                while (dt.Length > 0) {
                    var p = (Class2) ht[dt];
                    if (p == ct) {
                        isNested = true;
                        break;
                    }
                    dt = p.DeclaringType;
                }

                if (isNested) {
                    //Write("Skipping: " + ct2.FullName + " because it is a nested class of: " + ct.FullName);
                    continue;
                }

                RefItem ri = ct2.GetRef(ct.FullName);
                if (ri == null)
                    continue; // no references exist
    
                if (ri.Locations == CodeLocation.ConstructorBody) {
                    bool isDescendant = false;
                    // ConstructorBody is the only location typically because of the implicit "base()" call.
                    // Check if ct2 is a descendant class of ct. If so, don't count the reference.          
                    String bt = ct2.BaseType;
                    while (bt.Length > 0) {
                        var p = (Class2) ht[bt];
                        if (p == null)
                            break;

                        if (p == ct) {
                            isDescendant = true;
                            break;
                        }
                        bt = p.BaseType;
                    }

                    if (isDescendant)
                        continue; // skip, does not count as a reference
                }

                var list1 = (List<Class2>) htCluster[ct];
                var list2 = (List<Class2>) htCluster[ct2];
                if (list1 != list2) {
                    // group the nodes as part of the same cluster
                    List<Class2> list3 = new List<Class2>();
                    list3.AddRange(list1);
                    list3.AddRange(list2);
                    foreach (Class2 c2 in list3)
                        htCluster[c2] = list3;

                    //Write("Merged: [" + String.Join(", ", list1.Select(t => t.FullName)) + "] and [" + String.Join(", ", list2.Select(t => t.FullName)) + "]", sw2, console);
                }

                while (ct != null) {
                    if (ct2 != ct)
                        ct.Nbors.Add(ct2);

                    //Console.WriteLine(ct.FullName + ".Nbors.Add(" + ct2.FullName + ")");
                    ct = (Class2) ht[ct.DeclaringType];
                }

            }
        }

        // for each type that has a non-ConstructorBody reference, the ancestor types are counted as references.
        // for example, if there were 5 classes A to E, where each one extends the one before it, i.e: A < B < C < D < E
        // Suppose only class C is referenced somewhere in the code, then classes A & B are considered to be used,
        // but classes D & E are considered unused.
        foreach (Class2 ct in ctList) {
            if (ct.Nbors.Count == 0)
                continue;

            Class2 ct2 = ct;
            String bt = ct2.BaseType;
            while (bt.Length > 0) {
                var p = (Class2) ht[bt];
                if (p == null)
                    break;

                if (p.Nbors.Count == 0)
                    p.Nbors.Add(ct2);

                bt = p.BaseType;
                ct2 = p;
            }
        }

        List<List<Class2>> clusters = htCluster.Values.Cast<List<Class2>>().Distinct().ToList();
        List<List<Class2>> clusters2 = new List<List<Class2>>();
        foreach (List<Class2> list in clusters) {
            if (list.FirstOrDefault(t => t.IsEntryPoint) != null)
                continue; // the cluster that contains the Main() method is not reported as an isolated cluster

            // a cluster must have two or more non-related, non-nested classes
            var list2 = list.Where(t => t.DeclaringType.Length == 0).ToList();
            if (list2.Count > 1)
                clusters2.Add(list2);
        }

        if (clusters2.Count > 0) {
            Write(clusters2.Count + " isolated code clusters detected:", sw2, console);
            for (int i = 0; i < clusters2.Count; i++) {
                List<Class2> list = clusters2[i];
                list.Sort();
                Write("Cluster " + (i+1) + " has " + list.Count + " classes:", sw2, console);
                for (int j = 0; j < list.Count; j++) {
                    Write("  " + (j+1) + ". " + list[j].FullName + " (" + GetName(list[j].NodeType) + ")", sw2, console);
                }
                Write("", sw2, console);
            }
        }

        // the class that contains the entry point is not reported as unused
        // Don't report anonymous classes (classes that have "<>" in the name)
        List<Class2> ctUnused = ctList.Where(t => t.Nbors.Count == 0 && t.FullName.IndexOf("<>") < 0 && !t.IsEntryPoint).ToList();
        if (ctUnused.Count > 0) {               
            // group by NodeType
            Dictionary<NodeType, List<Class2>> dict = new Dictionary<NodeType, List<Class2>>();
            foreach (Class2 ct in ctUnused) {
                Class2 ct2 = ct;
                bool include = true;
                while (ct2.DeclaringType.Length > 0) {
                    var dt = (Class2) ht[ct2.DeclaringType];
                    if (dt.Nbors.Count == 0) {
                        // don't report a nested class if a parent class will be reported
                        include = false;
                        break;
                    }
                    ct2 = dt;
                }
                if (!include)
                    continue;

                List<Class2> list = null;
                if (!dict.TryGetValue(ct.NodeType, out list)) {
                    list = new List<Class2>();
                    dict[ct.NodeType] = list;
                }
                list.Add(ct);
            }

            List<NodeType> types = dict.Keys.ToList();
            types.Sort();
            foreach (NodeType key in types) {
                String keyName = GetName(key);  
                List<Class2> list = dict[key];
                Write("", sw2, console);
                if (list.Count > 1)
                    Write("The following " + list.Count + " " + keyName + "s are not used by other code:", sw2, console);
                else
                    Write("The following " + keyName + " is not used by other code:", sw2, console);

                for (int i = 0; i < list.Count; i++) {
                    Write((i + 1) + ": " + list[i].FullName, sw2, console);
                }
            }
        }

        if (sw2 != null) {
            sw2.Flush();
            sw2.Dispose();
        }
    }

}

}

尝试阅读.sln文件。 它将包含对所有使用项目的引用。 对于使用的每个项目,请打开.csproj文件。 compile中的所有文件都用于项目构建。 假设每个文件有一个类,您可以安全地删除其他文件。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM