{"id":13629480,"url":"https://github.com/Stepami/visitor-net","last_synced_at":"2025-04-17T09:34:09.329Z","repository":{"id":50350000,"uuid":"518502709","full_name":"Stepami/visitor-net","owner":"Stepami","description":"First-ever acyclic generic extensible typesafe implementation of Visitor pattern for .NET without any usage of dynamic cast","archived":false,"fork":false,"pushed_at":"2023-12-12T18:32:51.000Z","size":95,"stargazers_count":23,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-03-26T09:02:07.349Z","etag":null,"topics":["acyclic","compile-time","csharp","extensible","generics","typesafe","visitor-pattern"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Stepami.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-07-27T14:58:15.000Z","updated_at":"2024-04-15T13:38:37.147Z","dependencies_parsed_at":"2023-12-07T16:28:37.554Z","dependency_job_id":"9688355f-059b-48b0-8703-aa3b4e9f29bd","html_url":"https://github.com/Stepami/visitor-net","commit_stats":{"total_commits":28,"total_committers":1,"mean_commits":28.0,"dds":0.0,"last_synced_commit":"90ea1f61112cee7431d80973b8e4fa51a7aec7dd"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stepami%2Fvisitor-net","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stepami%2Fvisitor-net/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stepami%2Fvisitor-net/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stepami%2Fvisitor-net/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Stepami","download_url":"https://codeload.github.com/Stepami/visitor-net/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223689370,"owners_count":17186411,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acyclic","compile-time","csharp","extensible","generics","typesafe","visitor-pattern"],"created_at":"2024-08-01T22:01:11.713Z","updated_at":"2024-11-08T20:31:01.617Z","avatar_url":"https://github.com/Stepami.png","language":"C#","readme":"![logo](visitor-net-logo.jpg)\n\nStatus:\n\n![stars](https://img.shields.io/github/stars/stepami/visitor-net?style=flat-square\u0026cacheSeconds=604800)\n[![NuGet](https://img.shields.io/nuget/dt/Visitor.NET.svg)](https://www.nuget.org/packages/Visitor.NET/)\n\n# Visitor.NET\n\nFirst-ever acyclic generic extensible typesafe implementation of Visitor pattern for .NET **without any usage of dynamic cast**.\n\nWith Visitor.NET you can develop typesafe acyclic visitors even if you do not have access to source code of visitable structures.\n\n## Installation\n\n### NuGet\n\nInstall package : [https://www.nuget.org/packages/Visitor.NET](https://www.nuget.org/packages/Visitor.NET).\n\n### GitHub\n\n- Clone locally this github repository\n- Build the `Visitor.NET.sln` solution\n\n## Projects using Visitor.NET\n\n- [HydraScript](https://github.com/Stepami/hydrascript)\n\n## Usage\n\n### Basic Example\n\nLet's say we have some expression-tree-like hierarchy, implementing basic arithmetics, like this:\n\n```csharp\npublic abstract record BinaryTreeNode;\n\npublic record Operation(char Symbol, BinaryTreeNode Left, BinaryTreeNode Right) : BinaryTreeNode;\n\npublic record Number(double Value) : BinaryTreeNode;\n\npublic record Parenthesis(BinaryTreeNode Node) : BinaryTreeNode;\n```\nSo we may want to traverse it in order to, for example, compute expression result.\n\nFirst of all, we implement evaluator using [`IVisitor\u003c,\u003e`](Visitor.NET/IVisitor.cs) interface:\n\n```csharp\npublic class BinaryTreeEvaluator : VisitorBase\u003cBinaryTreeNode, double\u003e,\n    IVisitor\u003cOperation, double\u003e,\n    IVisitor\u003cNumber, double\u003e,\n    IVisitor\u003cParenthesis, double\u003e\n{\n    public double Visit(Operation visitable) =\u003e\n        visitable.Symbol switch\n        {\n            '+' =\u003e visitable.Left.Accept(This) + visitable.Right.Accept(This),\n            _ =\u003e throw new NotImplementedException()\n        };\n\n    public double Visit(Number visitable) =\u003e visitable.Value;\n\n    public double Visit(Parenthesis visitable) =\u003e visitable.Node.Accept(This);\n}\n```\n\nBut then we have to tell structures we visit that they are visitable.\n\nIt is done through [`IVisitable\u003c\u003e`](Visitor.NET/IVisitable.cs) interface implementation:\n\n```csharp\npublic abstract record BinaryTreeNode : IVisitable\u003cBinaryTreeNode\u003e\n{\n    public abstract TReturn Accept\u003cTReturn\u003e(\n        IVisitor\u003cBinaryTreeNode, TReturn\u003e visitor);\n}\n\npublic record Operation(\n    char Symbol,\n    BinaryTreeNode Left,\n    BinaryTreeNode Right) : BinaryTreeNode, IVisitable\u003cOperation\u003e\n{\n    public override TReturn Accept\u003cTReturn\u003e(\n        IVisitor\u003cBinaryTreeNode, TReturn\u003e visitor) =\u003e\n        Accept(visitor);\n\n    public TReturn Accept\u003cTReturn\u003e(\n        IVisitor\u003cOperation, TReturn\u003e visitor) =\u003e\n        visitor.Visit(this);\n}\n\npublic record Number(double Value) : BinaryTreeNode, IVisitable\u003cNumber\u003e\n{\n    public override TReturn Accept\u003cTReturn\u003e(\n        IVisitor\u003cBinaryTreeNode, TReturn\u003e visitor) =\u003e\n        Accept(visitor);\n\n    public TReturn Accept\u003cTReturn\u003e(\n        IVisitor\u003cNumber, TReturn\u003e visitor) =\u003e\n        visitor.Visit(this);\n}\n\npublic record Parenthesis(BinaryTreeNode Node) : BinaryTreeNode, IVisitable\u003cParenthesis\u003e\n{\n    public override TReturn Accept\u003cTReturn\u003e(\n        IVisitor\u003cBinaryTreeNode, TReturn\u003e visitor) =\u003e\n        Accept(visitor);\n\n    public TReturn Accept\u003cTReturn\u003e(\n        IVisitor\u003cParenthesis, TReturn\u003e visitor) =\u003e\n        visitor.Visit(this);\n}\n```\n\nBasically, if you have access to source code of structure you want \"visit\", it's better to always have implementation:\n\n```csharp\nreturn visitor.Visit(this);\n```\n\nIn case you would make `Visit` implementation procedure (i.e. have no returning value), use [`VisitUnit` type](https://en.wikipedia.org/wiki/Unit_type) as return type.\n\nSo, your method would look like this:\n\n```csharp\npublic class SomeVisitor : IVisitor\u003cSome\u003e\n{\n    public VisitUnit Visit(Some visitable)\n    {\n        //...\n        return default;\n    }\n}\n```\n### Adapter Usage\n\nLet's imagine you want to visit some structure defined outside of your project (library, dto, etc.):\n\n```csharp\npublic record LinkedListNode\u003cT\u003e(T Data, LinkedListNode\u003cT\u003e Next)\n{\n    public bool HasNext() =\u003e Next != null;\n}\n```\n\nSo we may define wrapper around instance of this type which would [became visitable](Visitor.NET/Adapter/VisitableAdapter.cs):\n\n```csharp\npublic class LinkedListToVisitableAdapter\u003cT\u003e :\n    VisitableAdapter\u003cLinkedListNode\u003cT\u003e\u003e,\n    IVisitable\u003cLinkedListToVisitableAdapter\u003cT\u003e\u003e\n{\n    public LinkedListToVisitableAdapter(LinkedListNode\u003cT\u003e data) :\n        base(data)\n    {\n    }\n\n    public override TReturn Accept\u003cTReturn\u003e(\n        IVisitor\u003cVisitableAdapter\u003cLinkedListNode\u003cT\u003e\u003e, TReturn\u003e visitor) =\u003e\n        Accept(visitor);\n\n    public TReturn Accept\u003cTReturn\u003e(\n        IVisitor\u003cLinkedListToVisitableAdapter\u003cT\u003e, TReturn\u003e visitor) =\u003e\n        visitor.Visit(this);\n}\n```\n\nThis adapter can be instantiated with [`VisitableAdapterFactory\u003c\u003e`](Visitor.NET/Adapter/VisitableAdapterFactory.cs) implementation:\n\n```csharp\npublic class LinkedListToVisitableAdapterFactory\u003cT\u003e :\n    VisitableAdapterFactory\u003cLinkedListNode\u003cT\u003e\u003e\n{\n    public override LinkedListToVisitableAdapter\u003cT\u003e Create(LinkedListNode\u003cT\u003e data) =\u003e\n        new(data);\n}\n```\n\nBringing it all together:\n\n```csharp\npublic class LinkedListNodePrinter\u003cT\u003e : VisitorNoReturnBase\u003cVisitableAdapter\u003cLinkedListNode\u003cT\u003e\u003e\u003e,\n    IVisitor\u003cLinkedListToVisitableAdapter\u003cT\u003e\u003e\n{\n    private readonly StringBuilder _sb = new();\n    private readonly VisitableAdapterFactory\u003cLinkedListNode\u003cT\u003e\u003e _factory;\n\n    public LinkedListNodePrinter(VisitableAdapterFactory\u003cLinkedListNode\u003cT\u003e\u003e factory) =\u003e\n        _factory = factory;\n\n    public VisitUnit Visit(LinkedListToVisitableAdapter\u003cT\u003e visitable)\n    {\n        var node = visitable.Data;\n        _sb.Append(node.Data);\n        if (node.HasNext())\n        {\n            var next = _factory.Create(node.Next);\n            _sb.Append(\"-\u003e\");\n            next.Accept(This);\n        }\n\n        return default;\n    }\n\n    public override string ToString() =\u003e _sb.ToString();\n}\n```\n\n# Visitor.NET.AutoVisitableGen\n\nIf you do not want implement visitable manually, you can do it automatically with incremental source generator.\n\nInstall package : [https://www.nuget.org/packages/Visitor.NET.AutoVisitableGen](https://www.nuget.org/packages/Visitor.NET.AutoVisitableGen).\n\nThen, rewrite the nodes type declarations like this:\n\n```csharp\npublic abstract record BinaryTreeNode : IVisitable\u003cBinaryTreeNode\u003e\n{\n    public abstract TReturn Accept\u003cTReturn\u003e(\n        IVisitor\u003cBinaryTreeNode, TReturn\u003e visitor);\n}\n\n[AutoVisitable\u003cBinaryTreeNode\u003e]\npublic partial record Operation(\n    char Symbol,\n    BinaryTreeNode Left,\n    BinaryTreeNode Right) : BinaryTreeNode;\n\n[AutoVisitable\u003cBinaryTreeNode\u003e]\npublic partial record Number(double Value) : BinaryTreeNode;\n\n[AutoVisitable\u003cBinaryTreeNode\u003e]\npublic partial record Parenthesis(BinaryTreeNode Node) : BinaryTreeNode;\n```","funding_links":[],"categories":["Contributors Welcome for those","Source Generators"],"sub_categories":["1. [ThisAssembly](https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisAssembly) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category","Patterns"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FStepami%2Fvisitor-net","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FStepami%2Fvisitor-net","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FStepami%2Fvisitor-net/lists"}