在 C# 中合并两个 .CS 文件以生成一个新类

Far*_*iaz 5 c#

我想合并两个 .cs 文件来创建第三个。任何人都可以请帮助我。

public partial class A 
{
 // some methods
}
Run Code Online (Sandbox Code Playgroud)

假设这段代码写在文件 A.cs 中

public partial class B 
{
 // some methods
}
Run Code Online (Sandbox Code Playgroud)

这段代码写在一个文件 B.cs 中。我想生成一个新的C.cs 具有所有代码A.csB.cs忽略命名空间的代码。

Vla*_*lad 6

我假设您确实想合并同一类的部分定义。如果您确实需要将不同的类合并为一个,则可以轻松调整代码,但不能保证它可以编译(例如,因为类可能具有同名的成员)。


由于符号含义,问题确实相当复杂:这取决于用途,因此在合并它们时需要非常小心。

所以最好的主意不是尝试手动分析代码语义,而是使用一个大锤子:Roslyn 分析器。

开始吧。

首先,您需要按照此处所述安装扩展开发工作负载。在此之后,您将能够创建一个独立的代码分析工具项目。

当你创建它时,你会得到很多有用的样板代码,如下所示:

class Program
{
    static async Task Main(string[] args)
    {
        // ...
        using (var workspace = MSBuildWorkspace.Create())
        {
            var solutionPath = args[0];
            WriteLine($"Loading solution '{solutionPath}'");

            var solution = await workspace.OpenSolutionAsync(solutionPath,
                    new ConsoleProgressReporter());
            WriteLine($"Finished loading solution '{solutionPath}'");

            // insert your code here
        }
    }

    private static VisualStudioInstance SelectVisualStudioInstance(
        VisualStudioInstance[] visualStudioInstances)
    {
        // ...
    }

    private class ConsoleProgressReporter : IProgress<ProjectLoadProgress>
    {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们填写需要的内容。

而不是// insert your code here让我们把下面的代码:

var targetClass = args[1];
var modifiedSolution = await MergePartialClasses(targetClass, solution);
workspace.TryApplyChanges(modifiedSolution);
Run Code Online (Sandbox Code Playgroud)

我们需要在MergePartialClasses. 类的名称应作为第二个命令行参数传递。

让我们首先在顶部添加以下用途:

using static System.Console;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
Run Code Online (Sandbox Code Playgroud)

现在我们可以从 main 方法开始。我已经把关于正在发生的事情的评论直接放在代码中。

static async Task<Solution> MergePartialClasses(string targetClass, Solution solution)
{
    // /sf/answers/2252579591/
    // we loop through the projects in the solution and process each of the projects
    foreach (var projectId in solution.ProjectIds)
    {
        var project = solution.GetProject(projectId);
        WriteLine($"Processing project {project.Name}");
        var compilation = await project.GetCompilationAsync();

        // finding the type which we want to merge
        var type = compilation.GetTypeByMetadataName(targetClass);
        if (type == null)
        {
            WriteLine($"Type {targetClass} is not found");
            return solution;
        }

        // look up number of declarations. if it's only 1, we have nothing to merge
        var declarationRefs = type.DeclaringSyntaxReferences;
        if (declarationRefs.Length <= 1)
        {
            WriteLine($"Type {targetClass} has only one location");
            return solution;
        }

        // I didn't implement the case of nested types, which would require to
        // split the outer class, too
        if (type.ContainingType != null)
            throw new NotImplementedException("Splitting nested types");

        // we'll accumulate usings and class members as we traverse all the definitions
        var accumulatedUsings = new List<UsingDirectiveSyntax>();
        var classParts = new List<ClassDeclarationSyntax>();
        foreach (var declarationRef in declarationRefs)
        {
            var declaration = (ClassDeclarationSyntax)await declarationRef.GetSyntaxAsync();
            // get hold of the usings
            var tree = declaration.SyntaxTree;
            var root = await tree.GetRootAsync();
            var usings = root.DescendantNodes().OfType<UsingDirectiveSyntax>();
            accumulatedUsings.AddRange(usings);
            // since we are trying to move the syntax into another file,
            // we need to expand everything in order to remove the dependency
            // on usings
            // in order to do it, we use a custom CSharpSyntaxRewriter (defined later)
            var document = project.GetDocument(tree);
            var expander = new AllSymbolsExpander(document);
            var expandedDeclaration = (ClassDeclarationSyntax)expander.Visit(declaration);
            classParts.Add(expandedDeclaration);
            // remove the old declaration from the place where it is
            // we can't just remove the whole file as it may contain some other classes
            var modifiedRoot =
                root.RemoveNodes(new[] { declaration }, SyntaxRemoveOptions.KeepNoTrivia);
            var modifiedDocument = document.WithSyntaxRoot(modifiedRoot);
            project = modifiedDocument.Project;
        }

        // now, sort the usings and remove the duplicates
        // in order to use DistinctBy, I added MoreLinq nuget package and added
        // using MoreLinq; at the beginning
        // /sf/answers/2384430261/
        var sortedUsings = accumulatedUsings
                .DistinctBy(x => x.Name.ToString())
                .OrderBy(x => x.StaticKeyword.IsKind(SyntaxKind.StaticKeyword) ?
                                  1 : x.Alias == null ? 0 : 2)
                .ThenBy(x => x.Alias?.ToString())
                .ThenByDescending(x => x.Name.ToString().StartsWith(nameof(System) + "."))
                .ThenBy(x => x.Name.ToString());

        // now, we have to merge the class definitions.
        // split the name into namespace and class name
        var (nsName, className) = SplitName(targetClass);

        // gather all the attributes
        var attributeLists = List(classParts.SelectMany(p => p.AttributeLists));
        // modifiers must be the same, so we are taking them from the
        // first definition, but remove partial if it's there
        var modifiers = classParts[0].Modifiers;
        var partialModifier = modifiers.FirstOrDefault(
                m => m.Kind() == SyntaxKind.PartialKeyword);
        if (partialModifier != null)
            modifiers = modifiers.Remove(partialModifier);
        // gather all the base types
        var baseTypes =
                classParts
                    .SelectMany(p => p.BaseList?.Types ?? Enumerable.Empty<BaseTypeSyntax>())
                    .Distinct()
                    .ToList();
        var baseList = baseTypes.Count > 0 ? BaseList(SeparatedList(baseTypes)) : null;
        // and constraints (I hope that Distinct() works as expected)
        var constraintClauses =
                List(classParts.SelectMany(p => p.ConstraintClauses).Distinct());

        // now, we construct class members by pasting together the accumulated
        // per-part member lists
        var members = List(classParts.SelectMany(p => p.Members));

        // now we can build the class declaration
        var classDef = ClassDeclaration(
            attributeLists: attributeLists,
            modifiers: modifiers,
            identifier: Identifier(className),
            typeParameterList: classParts[0].TypeParameterList,
            baseList: baseList,
            constraintClauses: constraintClauses,
            members: members);

        // if there was a namespace, let's put the class inside it 
        var body = (nsName == null) ?
            (MemberDeclarationSyntax)classDef :
            NamespaceDeclaration(IdentifierName(nsName)).AddMembers(classDef);

        // now create the compilation unit and insert it into the project
        // http://roslynquoter.azurewebsites.net/
        var newTree = CompilationUnit()
                          .WithUsings(List(sortedUsings))
                          .AddMembers(body)
                          .NormalizeWhitespace();
        var newDocument = project.AddDocument(className, newTree);
        var simplifiedNewDocument = await Simplifier.ReduceAsync(newDocument);
        project = simplifiedNewDocument.Project;

        solution = project.Solution;
    }

    // finally, return the modified solution
    return solution;
}
Run Code Online (Sandbox Code Playgroud)

其余的是AllSymbolsExpander,它只调用Simplifier.ExpandAsync每个节点:

class AllSymbolsExpander : CSharpSyntaxRewriter
{
    Document document;
    public AllSymbolsExpander(Document document)
    {
        this.document = document;
    }

    public override SyntaxNode VisitAttribute(AttributeSyntax node) =>
        Expand(node);
    public override SyntaxNode VisitAttributeArgument(AttributeArgumentSyntax node) =>
        Expand(node);
    public override SyntaxNode VisitConstructorInitializer(ConstructorInitializerSyntax node) =>
        Expand(node);
    public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node) =>
        Expand(node);
    public override SyntaxNode VisitXmlNameAttribute(XmlNameAttributeSyntax node) =>
        Expand(node);
    public override SyntaxNode VisitTypeConstraint(TypeConstraintSyntax node) =>
        Expand(node);

    public override SyntaxNode DefaultVisit(SyntaxNode node)
    {
        if (node is ExpressionSyntax ||
            node is StatementSyntax ||
            node is CrefSyntax ||
            node is BaseTypeSyntax)
            return Expand(node);
        return base.DefaultVisit(node);
    }

    SyntaxNode Expand(SyntaxNode node) =>
        Simplifier.ExpandAsync(node, document).Result; //? async-counterpart?
}
Run Code Online (Sandbox Code Playgroud)

和琐碎的功能SplitName

static (string, string) SplitName(string name)
{
    var pos = name.LastIndexOf('.');
    if (pos == -1)
        return (null, name);
    else
        return (name.Substring(0, pos), name.Substring(pos + 1));
}
Run Code Online (Sandbox Code Playgroud)

就这样!