やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

C#コードツリー構築(名前空間、クラス特化)失敗

 失敗した。原因不明。

成果物

前回まで

失敗した出力結果

using System;

MyNamespace
{
    MyClass
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello world");
        }
    }
}

 namespace, classのキーワードが出ていない。

期待値

...
namespace MyNamespace
{
    class MyClass
    {
...

原因

 継承したGenerate()メソッドが正しく呼ばれていない。子でなく親のメソッドが呼ばれている。つまりCodeNamespaceCodeClassでなくCodeNodeのメソッドが呼ばれてしまっている。なぜ? わからない。

推測

 もしや別の型扱いなのか? interfaceを継承したクラスをさらに継承した孫クラスは、そのinterfaceを持っていない別の型として見られるの? INodeというinterface型でAdd()GetChildren()したせいで、親であるCodeNodeクラスになってしまったのか?

対処案

 仮にそうだとして、じゃあどうやって子クラスのメソッドを呼べばいいんだ? ふつう何層も継承したら最子孫クラスのメソッドが呼ばれるはずでは? interfaceを取っ払ったら呼出元は何の型にすればいいのさ? 親の型にしたらそれこそ親メソッドが呼ばれてしまうし。わからん!

コード

 継承関係は以下。

  • INode
    • CodeNode
      • CodeBlock
        • CodeNamespace
        • CodeClass

CodeNamespace.cs

class CodeNamespace: CodeBlock
{
    public CodeNamespace(string value, StyleType style=StyleType.NewLine) : base(value, style) {}
    public new string Generate()  => $"namespace ${Value}";
}

 これ。namespaceという文字列を結合した値を返している。こいつが呼ばれるはずだった。

CodeClass.cs

class CodeClass: CodeBlock
{
    public CodeClass(string value, StyleType style=StyleType.NewLine) : base(value, style) {}
    public new string Generate()  => $"class ${Value}";
}

 これ。classという文字列を結合した値を返している。こいつが呼ばれるはずだった。

 以下は今回無関係。

CodeBlock.cs

class CodeBlock: CodeNode
{
    public enum StyleType { None, Minimum, Space, NewLine };
    public StyleType Style { get; private set; }
    public CodeBlock(string value, StyleType style=StyleType.NewLine) : base(value) => Style = style;
}

 以下のGenerate()。こいつが呼ばれているせいでnamespaceclassが出なかったと思われる。

CodeNode.cs

class CodeNode : INode
{
    protected string Value { get; }
    protected IList<INode> children { get; set; }
    protected IReadOnlyList<INode> Children { get; }
    public CodeNode(string value)
    {
        children = new List<INode>();
        Children = new ReadOnlyCollection<INode>(children);
        Value = value;
    }
    public IReadOnlyList<INode> GetChildren() => Children;
    public INode Add(INode node) { children.Add(node); return this; }
    public string Generate() => Value;
}

 以下は今回無関係? いやINodeが問題か? わからん。

CodeTree.cs

class CodeTree
{
    protected IList<INode> children;
    public IReadOnlyList<INode> Children { get; }
    public CodeTree()
    {
        children = new List<INode>();
        Children = new ReadOnlyCollection<INode>(children);
    }
    public CodeTree Add(INode node) { children.Add(node); return this; }
    public IReadOnlyList<INode> GetChildren() => Children;
}

 以下でGenerate()を呼び出している。

CodeGenerator.cs

class CodeGenerator
{
    public string Indent { get; set; } = "    ";
    private StringBuilder builder;
    private BlockCodeGenerator blockGen;
    public CodeGenerator()
    {
        this.builder = new StringBuilder();
        this.blockGen = new BlockCodeGenerator(this.builder, Indent);
    }
    public string Generate(CodeTree tree)
    {
        builder.Clear();
        foreach(INode node in tree.GetChildren()) { _Generate(node, 0); }
        return builder.ToString().TrimStart('\n'); // 先頭の改行を1つ削除
    }
    private void _Generate(INode node, int indent)
    {
        builder.Append("\n");
        builder.Insert(builder.Length, Indent, indent).Append(node.Generate());
        blockGen.Blocking(node, indent);
        if (0 < node.GetChildren().Count)
        {
            indent++;
            foreach(INode n in node.GetChildren()) { _Generate(n, indent); }
            indent--;
        }
        blockGen.Blocked(node, indent);
    }

    class BlockCodeGenerator
    {
        ...
    }
}

 以下が呼出元。ちゃんとCodeNamespaceCodeClassを使っている。

Program.cs

class Program
{
    static void Main(string[] args)
    {
        var gen = new CodeGenerator();
        var tree = new CodeTree()
            .Add(new CodeNode("using System;"))
            .Add(new CodeNode(""))
            .Add(new CodeNamespace("MyNamespace")
                .Add(new CodeClass("MyClass")
                    .Add(new CodeBlock("static void Main(string[] args)")
                        .Add(new CodeNode(@"Console.WriteLine(""Hello world"");")))));
        Console.WriteLine(gen.Generate(tree));
    }
}

所感

 これはC#言語に対する理解が不足しているからだと思われる。チュートリアル完了させたから書けるだろうと調子こいてたらこのザマだよ。続きのC#ツアーをやるべきか。

 その前にC#構文のセミコロンについてと、dotnet/cscコマンドについて少しまとめておきたい。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux