【问题标题】:How to script removal of redundant using directives from the given C# source file?如何从给定的 C# 源文件中编写删除冗余指令的脚本?
【发布时间】:2021-02-21 22:50:13
【问题描述】:

Resharper 的 cleanupcode.exe 工具似乎不知道这样做。

也许其他一些工具可以,但我没有找到任何工具。

到目前为止,尝试使用 Roslyn SemanticModel 自己做这件事还没有成功,因为我不知道模型需要什么样的处理,也找不到相关文档(或错过了)。

所以,问题是 - 是否有一种工具可以识别实际使用的命名空间,或者甚至可以立即从给定的源文件中删除多余的 using 指令?关于如何处理语义模型的想法也很受欢迎。

编辑 1

考虑以下 C# 源代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace CSTool.Tests.Input
{
    public class Sample
    {
        public void Func()
        {
            static ICollection<ArrayList> action(string _) => null;

            Console.WriteLine(action(Directory.GetFiles(Directory.GetCurrentDirectory()).FirstOrDefault())?.Count);
        }
    }
}

那么,我怎样才能确认 using 指令都是必需的呢?我尝试了以下代码:

private static IEnumerable<string> GetNamespaceSymbol(ISymbol symbol)
{
    if (symbol != null && symbol.ContainingNamespace != null)
    {
        yield return symbol.ContainingNamespace.Name;
    }
}

public static HashSet<string> Run(CSharpCompilation compilation, SyntaxTree tree)
{
    var semanticModel = compilation.GetSemanticModel(tree);
    return tree.GetRoot().DescendantNodes().SelectMany(node =>
        GetNamespaceSymbol(semanticModel.GetSymbolInfo(node).Symbol)).ToHashSet();
}

但它返回以下无意义的结果 - {"", "CSTool", "System", "Tests"}

根本不需要。

我被指出了以下问题 - https://stackoverflow.com/a/9983275/2501279。但显然自 2012 年以来发生了很大变化,而今天的答案没有任何意义。至少对我来说。

编辑 2

这是我获得树和编译对象的方法:

public List<string> Test(string text, Type[] types)
{
    var tree = CSharpSyntaxTree.ParseText(text);
    var compilation = CSharpCompilation
        .Create("test")
        .AddReferences(types.Select(t => MetadataReference.CreateFromFile(t.Assembly.Location)))
        .AddSyntaxTrees(tree);
    return CleanupNamespacesCmd.Run(compilation, tree).OrderBy(ns => ns).ToList();
}

编辑 3

所以,我尝试了新方法。代码如下:

public static void Run(string projectFilePath)
{
    var project = new Project(projectFilePath);
    var outDir = project.GetPropertyValue("OutDir");
    using var a = AssemblyDefinition.ReadAssembly(project.GetPropertyValue("TargetPath"));
    var trees = MapTypesCmd
        .YieldCSFiles(project)
        .Select(filePath => CSharpSyntaxTree.ParseText(File.ReadAllText(filePath), path: filePath));
    var compilation = CSharpCompilation
        .Create(project.GetPropertyValue("AssemblyName"))
        .AddReferences(a.MainModule.AssemblyReferences.Select(CreateMetadataReference))
        .AddSyntaxTrees(trees);

    foreach (var tree in compilation.SyntaxTrees)
    {
        var model = compilation.GetSemanticModel(tree);
        var diag = model.GetDiagnostics();
    }

    MetadataReference CreateMetadataReference(AssemblyNameReference asmRef)
    {
        var asm = a.MainModule.AssemblyResolver.Resolve(asmRef);
        var path = asm.MainModule.FileName;
        if (!Path.IsPathRooted(path))
        {
            path = outDir + asm.MainModule.Name;
        }

        return MetadataReference.CreateFromFile(path);
    }
}

代码已经编译好了,所以我使用Mono.Cecil读取程序集引用列表,将它们转换为MetadataReferences,并在创建CSharpCompilation对象时使用。

但是,diag 数组包含虚假的诊断消息,例如这个 - C:\xyz\tip\DB\DL\Db\FilterDB.cs(25,24): error CS0518: Predefined type ' System.String' 未定义或导入

这太假了。监视窗口告诉我 compilation.References.First().Display 等于 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.12\mscorlib.dll 并且正如我之前提到的 - 代码编译干净利落。

我做错了什么?

【问题讨论】:

  • 你试过this回答吗?
  • 我尝试了类似的方法,但并不完全如此。并错过了那个答案。我会检查并报告
  • 没有骰子。 Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel 类没有方法 GetSemanticInfo

标签: c# roslyn


【解决方案1】:

因此,这个特定问题在 Roslyn 中是特殊情况 - Roslyn 编译器会针对使用它知道未使用的语句发出诊断,然后您可以从那里处理这些语句。 GetSemanticModel.GetDiagnostics() 应该让您了解它们。 IDE 使用这些诊断来实现我们的“删除未使用的使用”功能。

为什么要用这种奇怪的方法?因为试图从其他 API 来回答这个问题几乎是不可能的。在 C# 中有一些疯狂的边缘情况,您可以删除 using 并且代码仍然可以编译,但是重载分辨率发生了变化,因此您的代码改变了含义。我们将它们视为“已使用”,但如果您的代码不仔细了解重载解析如何考虑命名空间,您就不能这样做。

更一般地说,根据您尝试的目的,我们还有其他可能更有用的工具或 API。

【讨论】:

  • 非常感谢您提供如此详细的回答。我正在尝试这样做 - 脚本删除未使用的命名空间。
  • 你能评论一下EDIT 3吗?
  • @mark:只是为了确认一下,您要构建什么?如果您只需要一个工具来运行磁盘上的解决方案并删除所有使用...我认为我们已经有了。
  • 我需要一个工具来从给定项目中删除未使用的命名空间,而不是解决方案。它必须是命令行工具,而不是 GUI。如果已经有东西——它是什么?我找不到。
  • 所以我们有 dotnet 格式,可以修复一些诊断等。我在内部询问了团队如何配置它,因为这对我来说并不明显。 (这有点尴尬,因为我应该知道,但是......)
猜你喜欢
  • 1970-01-01
  • 2011-12-20
  • 2019-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多