Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Stepami/visitor-net
First-ever acyclic generic extensible typesafe implementation of Visitor pattern for .NET without any usage of dynamic cast
https://github.com/Stepami/visitor-net
acyclic compile-time csharp extensible generics typesafe visitor-pattern
Last synced: about 1 month ago
JSON representation
First-ever acyclic generic extensible typesafe implementation of Visitor pattern for .NET without any usage of dynamic cast
- Host: GitHub
- URL: https://github.com/Stepami/visitor-net
- Owner: Stepami
- License: gpl-3.0
- Created: 2022-07-27T14:58:15.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2023-12-12T18:32:51.000Z (about 1 year ago)
- Last Synced: 2024-03-26T09:02:07.349Z (9 months ago)
- Topics: acyclic, compile-time, csharp, extensible, generics, typesafe, visitor-pattern
- Language: C#
- Homepage:
- Size: 92.8 KB
- Stars: 23
- Watchers: 2
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- RSCG_Examples - https://github.com/Stepami/visitor-net - net (Contributors Welcome for those / 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)
README
![logo](visitor-net-logo.jpg)
Status:
![stars](https://img.shields.io/github/stars/stepami/visitor-net?style=flat-square&cacheSeconds=604800)
[![NuGet](https://img.shields.io/nuget/dt/Visitor.NET.svg)](https://www.nuget.org/packages/Visitor.NET/)# Visitor.NET
First-ever acyclic generic extensible typesafe implementation of Visitor pattern for .NET **without any usage of dynamic cast**.
With Visitor.NET you can develop typesafe acyclic visitors even if you do not have access to source code of visitable structures.
## Installation
### NuGet
Install package : [https://www.nuget.org/packages/Visitor.NET](https://www.nuget.org/packages/Visitor.NET).
### GitHub
- Clone locally this github repository
- Build the `Visitor.NET.sln` solution## Projects using Visitor.NET
- [HydraScript](https://github.com/Stepami/hydrascript)
## Usage
### Basic Example
Let's say we have some expression-tree-like hierarchy, implementing basic arithmetics, like this:
```csharp
public abstract record BinaryTreeNode;public record Operation(char Symbol, BinaryTreeNode Left, BinaryTreeNode Right) : BinaryTreeNode;
public record Number(double Value) : BinaryTreeNode;
public record Parenthesis(BinaryTreeNode Node) : BinaryTreeNode;
```
So we may want to traverse it in order to, for example, compute expression result.First of all, we implement evaluator using [`IVisitor<,>`](Visitor.NET/IVisitor.cs) interface:
```csharp
public class BinaryTreeEvaluator : VisitorBase,
IVisitor,
IVisitor,
IVisitor
{
public double Visit(Operation visitable) =>
visitable.Symbol switch
{
'+' => visitable.Left.Accept(This) + visitable.Right.Accept(This),
_ => throw new NotImplementedException()
};public double Visit(Number visitable) => visitable.Value;
public double Visit(Parenthesis visitable) => visitable.Node.Accept(This);
}
```But then we have to tell structures we visit that they are visitable.
It is done through [`IVisitable<>`](Visitor.NET/IVisitable.cs) interface implementation:
```csharp
public abstract record BinaryTreeNode : IVisitable
{
public abstract TReturn Accept(
IVisitor visitor);
}public record Operation(
char Symbol,
BinaryTreeNode Left,
BinaryTreeNode Right) : BinaryTreeNode, IVisitable
{
public override TReturn Accept(
IVisitor visitor) =>
Accept(visitor);public TReturn Accept(
IVisitor visitor) =>
visitor.Visit(this);
}public record Number(double Value) : BinaryTreeNode, IVisitable
{
public override TReturn Accept(
IVisitor visitor) =>
Accept(visitor);public TReturn Accept(
IVisitor visitor) =>
visitor.Visit(this);
}public record Parenthesis(BinaryTreeNode Node) : BinaryTreeNode, IVisitable
{
public override TReturn Accept(
IVisitor visitor) =>
Accept(visitor);public TReturn Accept(
IVisitor visitor) =>
visitor.Visit(this);
}
```Basically, if you have access to source code of structure you want "visit", it's better to always have implementation:
```csharp
return visitor.Visit(this);
```In 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.
So, your method would look like this:
```csharp
public class SomeVisitor : IVisitor
{
public VisitUnit Visit(Some visitable)
{
//...
return default;
}
}
```
### Adapter UsageLet's imagine you want to visit some structure defined outside of your project (library, dto, etc.):
```csharp
public record LinkedListNode(T Data, LinkedListNode Next)
{
public bool HasNext() => Next != null;
}
```So we may define wrapper around instance of this type which would [became visitable](Visitor.NET/Adapter/VisitableAdapter.cs):
```csharp
public class LinkedListToVisitableAdapter :
VisitableAdapter>,
IVisitable>
{
public LinkedListToVisitableAdapter(LinkedListNode data) :
base(data)
{
}public override TReturn Accept(
IVisitor>, TReturn> visitor) =>
Accept(visitor);public TReturn Accept(
IVisitor, TReturn> visitor) =>
visitor.Visit(this);
}
```This adapter can be instantiated with [`VisitableAdapterFactory<>`](Visitor.NET/Adapter/VisitableAdapterFactory.cs) implementation:
```csharp
public class LinkedListToVisitableAdapterFactory :
VisitableAdapterFactory>
{
public override LinkedListToVisitableAdapter Create(LinkedListNode data) =>
new(data);
}
```Bringing it all together:
```csharp
public class LinkedListNodePrinter : VisitorNoReturnBase>>,
IVisitor>
{
private readonly StringBuilder _sb = new();
private readonly VisitableAdapterFactory> _factory;public LinkedListNodePrinter(VisitableAdapterFactory> factory) =>
_factory = factory;public VisitUnit Visit(LinkedListToVisitableAdapter visitable)
{
var node = visitable.Data;
_sb.Append(node.Data);
if (node.HasNext())
{
var next = _factory.Create(node.Next);
_sb.Append("->");
next.Accept(This);
}return default;
}public override string ToString() => _sb.ToString();
}
```# Visitor.NET.AutoVisitableGen
If you do not want implement visitable manually, you can do it automatically with incremental source generator.
Install package : [https://www.nuget.org/packages/Visitor.NET.AutoVisitableGen](https://www.nuget.org/packages/Visitor.NET.AutoVisitableGen).
Then, rewrite the nodes type declarations like this:
```csharp
public abstract record BinaryTreeNode : IVisitable
{
public abstract TReturn Accept(
IVisitor visitor);
}[AutoVisitable]
public partial record Operation(
char Symbol,
BinaryTreeNode Left,
BinaryTreeNode Right) : BinaryTreeNode;[AutoVisitable]
public partial record Number(double Value) : BinaryTreeNode;[AutoVisitable]
public partial record Parenthesis(BinaryTreeNode Node) : BinaryTreeNode;
```