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

时间:2019-02-08 11:41:56

标签: c#

我想合并两个.cs文件以创建第三个文件。谁能帮我。

public partial class A 
{
 // some methods
}

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

public partial class B 
{
 // some methods
}

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

2 个答案:

答案 0 :(得分:4)

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


由于符号含义,问题确实很复杂:它取决于用法,因此在合并它们时必须格外小心。

因此,最好的主意不是尝试手动分析代码语义,而是使用一个大锤子:罗斯林分析器。

开始吧。

首先,您需要按照here的说明安装Extension Development Workload。之后,您将能够创建一个独立代码分析工具项目。

创建它时,您将获得很多有用的样板代码,如下所示:

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>
    {
        // ...
    }
}

让我们填写所需的内容。

让我们放置以下代码代替// insert your code here

var targetClass = args[1];
var modifiedSolution = await MergePartialClasses(targetClass, solution);
workspace.TryApplyChanges(modifiedSolution);

我们需要在MergePartialClasses中实现逻辑。该类的名称应作为第二个命令行参数传递。

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

using static System.Console;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

现在我们可以从main方法开始。我已经将有关正在发生的事情的注释直接放入了代码中。

static async Task<Solution> MergePartialClasses(string targetClass, Solution solution)
{
    // https://stackoverflow.com/a/32179708/276994
    // 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
        // https://stackoverflow.com/a/34063289/276994
        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;
}

剩下的是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?
}

和普通函数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));
}

仅此而已!

答案 1 :(得分:0)

我想合并所有代码生成的文件以创建一个文件。经过大量搜索,我通过创建一个新类来完成此任务。读取所有首先生成的代码文件,在新创建的类中创建它们的对象,然后在其中调用它们的Up()和Down()方法。 注意:编写了一个单独的方法,请读取所有命名空间不同的名称。如果有人想要代码,我也可以共享我的代码示例。