yotiky Tech Blog

とあるエンジニアの備忘録

C# - 相対パスでオブジェクト参照を辿るツリー構造のサンプル

ツリー構造を成すクラスの親クラス。

public abstract class Node
{
    protected const string Slash = "/";

    [XmlIgnore] public Node Parent { get; set; }

    public Node GetReferencedNode(string referencePath)
    {
        var path = referencePath;
        var target = this;

        while (path != "")
        {
            (path, target) = target.GetNode(path);
        }
        return target;
    }

    public abstract void SetParent(Node parent);

    protected abstract (string afterPath, Node result) GetNode(string path);

    protected (bool, string, Node) IsTargetNodeSelfOrParent(string path)
    {
        if (path.StartsWith($"..{Slash}"))
        {
            var afterPath = path.Substring(2 + Slash.Length);
            return (true, afterPath, Parent);
        }
        if (path.StartsWith($".{Slash}"))
        {
            var afterPath = path.Substring(1 + Slash.Length);
            return (true, afterPath, this);
        }

        return (false, path, null);
    }
    protected (bool, string) IsTargetNode(string path, string targetItemName)
    {
        if (path == targetItemName)
        {
            return (true, "");
        }
        if (path.StartsWith($"{targetItemName}{Slash}"))
        {
            var index = path.IndexOf(Slash);
            var afterPath = path.Substring(index + 1);
            return (true, afterPath);
        }
        return (false, path);
    }
    protected (bool, string, int) IsTargetNodeCollection(string path, string targetItemName)
    {
        if (path == targetItemName)
        {
            return (true, "", 0);
        }
        if (path.StartsWith($"{targetItemName}{Slash}"))
        {
            var index = path.IndexOf(Slash);
            var afterPath = path.Substring(index + 1);
            return (true, afterPath, 0);
        }
        if (path.StartsWith($"{targetItemName}["))
        {
            var arrayIndexStart = path.IndexOf("[");
            var arrayIndexEnd = path.IndexOf("]");
            var arrayIndex = int.Parse(path.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1));
            var index = path.IndexOf(Slash);
            if (index < 0)
                return (true, "", arrayIndex);
            var afterPath = path.Substring(index + 1);
            return (true, afterPath, arrayIndex);
        }

        return (false, path, -1);
    }
}

ツリー構造のサンプル。

[Serializable]
public class ClassA : Node
{
    public ClassB B ;
    public ClassC C { get; set; }
    public List<ClassD> D { get; set; }

    public override void SetParent(Node parent)
    {
        this.Parent = parent;
        
        B.SetParent(this);
        C.SetParent(this);
        foreach (var d in D)
            d.SetParent(this);
    }

    protected override (string, Node) GetNode(string path)
    {
        {
            var (isTarget, afterPath, node) = base.IsTargetNodeSelfOrParent(path);
            if (isTarget) { return (afterPath, node); }
        }

        {
            var (isTarget, afterPath) = base.IsTargetNode(path, nameof(B));
            if (isTarget) { return (afterPath, B); }
        }
        {
            var (isTarget, afterPath) = base.IsTargetNode(path, nameof(C));
            if (isTarget) { return (afterPath, C); }
        }
        {
            var (isTarget, afterPath, index) = base.IsTargetNodeCollection(path, nameof(D));
            if (isTarget) { return (afterPath, D[index]); }
        }
        return (path, null);
    }
}


[Serializable]
public class ClassB : Node
{
    public string Value { get; set; }
    
    public ClassB BB { get; set; }

    public override void SetParent(Node parent)
    {
        this.Parent = parent;

        BB?.SetParent(this);
    }

    protected override (string, Node) GetNode(string path)
    {
        {
            var (isTarget, afterPath, node) = base.IsTargetNodeSelfOrParent(path);
            if (isTarget) { return (afterPath, node); }
        }

        {
            var (isTarget, afterPath) = base.IsTargetNode(path, nameof(BB));
            if (isTarget) { return (afterPath, BB); }
        }
        return (path, null);
    }
}

[Serializable]
public class ClassC : Node
{
    public string Value { get; set; }
    
    public ClassC CC { get; set; }

    public override void SetParent(Node parent)
    {
        this.Parent = parent;

        CC?.SetParent(this);
    }

    protected override (string, Node) GetNode(string path)
    {
        {
            var (isTarget, afterPath, node) = base.IsTargetNodeSelfOrParent(path);
            if (isTarget) { return (afterPath, node); }
        }

        {
            var (isTarget, afterPath) = base.IsTargetNode(path, nameof(CC));
            if (isTarget) { return (afterPath, CC); }
        }
        return (path, null);
    }
}

[Serializable]
public class ClassD : Node
{   
    public ClassDD DD { get; set; }

    public override void SetParent(Node parent)
    {
        this.Parent = parent;

        DD?.SetParent(this);
    }

    protected override (string, Node) GetNode(string path)
    {
        {
            var (isTarget, afterPath, node) = base.IsTargetNodeSelfOrParent(path);
            if (isTarget) { return (afterPath, node); }
        }

        {
            var (isTarget, afterPath) = base.IsTargetNode(path, nameof(DD));
            if (isTarget) { return (afterPath, DD); }
        }
        return (path, null);
    }
}

[Serializable]
public class ClassDD : Node
{
    public string Value { get; set; }

    public override void SetParent(Node parent)
    {
        this.Parent = parent;
    }

    protected override (string, Node) GetNode(string path)
    {
        {
            var (isTarget, afterPath, node) = base.IsTargetNodeSelfOrParent(path);
            return (afterPath, node);
        }
    }
}
void Main()
{
    var a = new ClassA()
    {
        B = new ClassB
        {
            Value = "B.Value",
            BB = new ClassB { Value = "BB.Value" }
        },
        C = new ClassC
        {
            Value = "C.Value",
            CC = new ClassC { Value = "CC.Value" }
        },
        D = new List<ClassD>
        {
            new ClassD{ DD = new ClassDD{ Value = "DD[0].Value"}},
            new ClassD{ DD = new ClassDD{ Value = "DD[1].Value"}},
            new ClassD{ DD = new ClassDD{ Value = "DD[2].Value"}},
        }
    };
    a.SetParent(null);

    // 途中XMLを介してみる
    var writer = new StringWriter();
    var serializer = new XmlSerializer(typeof(ClassA));
    serializer.Serialize(writer, a);

    var xml = writer.ToString();
    xml.Dump();

    var deserialized = (ClassA)serializer.Deserialize(new StringReader(xml));
    deserialized.SetParent(null);

    var path ="../../D[0]/DD";
    var target = deserialized.B.BB.GetReferencedNode(path);
    
    target.Dump();
}

取得結果。