Skip to content
/ Byter Public

Byter: C# library for serializing and deserializing data to and from bytes, supporting unlimited complexity and depth.⚡

License

Notifications You must be signed in to change notification settings

alec1o/Byter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

⭐ Your star is the light at the end of our tunnel.
Lead us out of the darkness by starring Byter on GitHub.
Star me please, I beg you! 💙

powered by ALEC1O
Project

Get basic information about this project called Byter

Overview
Byter is a C# serialization library for primitive data types, including booleans, numbers, chars, enums, strings, DateTime, BigInteger, bytes, and complex types like classes, structs, arrays, and lists, supporting unlimited complexity and depth.

Website
Repository: github.com/alec1o/byter

Contributions

Thanks a lot <3



Installing

Official publisher

Nuget .NET CLI Netly
Install on Nuget
dotnet add package Byter --version 4.0.0

Versions

Notable changes

v1.x.x v2.x.x v3.x.x v4x.x
Stable Stable Stable Stable
Main (Reader & Writer) Types:
  byte   bool   byte[]
  short   ushort   int
  uint   long   ulong
  float   double   char
  string
New (Reader & Writer) Types:
Float2 (Vector2)
Float3 (Vector3)
Float4 (Vector4 / Quaternion)
Bug Fix. (Reader & Writer)

Support: *Primitive

New usage paradigms *Primitive

*Primitive Types:
  bool   byte
  char   short   ushort
  int   uint   float
  long   byte[]   ulong   double
  string
   *highlights
  enum*   sbyte*   DateTime*
  decimal*   class*   struct*
  array*   list*   BigInteger*
Bug Fix. (Primitive & Extension)

Support: *Concat Bytes

Fix. Compilation warning.
Used by. Netly v2 Used by. Netly v3 Used by. Netly v4 (under dev stage) Used by. Netly v4

Usage

Integration and interaction example codes

v1.x.x
v2.x.x
📄 Writer

Constructor

  • () : Writer
    Create instance with empty internal buffer
  • (Writer writer) : Writer
    Create instance and copy buffer of (Writer) as internal buffer
  • (ref Writer writer) : Writer
    Create instance and copy buffer of (ref Writer) as internal buffer

Proprieties

  • Length : int
    Return buffer length.

Methods

  • Write(T value) : void
    Write content in internal buffer
  • GetBytes() : byte[]
    Return buffer from (Writer) instance as byte[])
  • GetList() : List<byte>
    Return buffer from (Writer) instance as List<byte>
  • Clear(): void
    Clear internal buffer from (Writer) instance
📄 Reader

Constructor

  • (byte[] buffer) : Reader
    Create instance using (Byte[]) as internal buffer
  • (Writer writer) : Reader
    Create instance using (Writer) as internal buffer
  • (ref Writer writer) : Reader
    Create instance using (ref Writer) as internal buffer

Proprieties

  • Success : bool
    Return true if deserialized successful.
  • Position : int
    Return current read index.
  • Length : int
    Return buffer length.

Methods

  • Seek(int position) : void
    Move position (internal buffer index)
  • Read<T>() : T
    Read content from iternal buffer.
  • Read<string>(Encoding encoding) : string
    Read custom encoding string.
📄 Example
  • Writer
    using Byter;
    
    Writer w = new();
    
    // write data
    
    w.Write("Powered by ALEC1O");
    w.Write("由 ALEC1O 提供支持", Encoding.UTF32);
    w.Write((int)1000000);` // 1.000.000
    w.Write((char)'A');
    w.Write((long)-1000000000); // -100.0000.000
    w.Write((byte[])[0, 1, 2, 3]);
    
    // Float(1|2|3) only available in version 2
    w.Write(new Float2(-100F, 300F));
    w.Write(new Float3(-100F, 300F, 600F));
    w.Write(new Float4(-100F, 300F, 600F, 900F));
    
    // get buffer
    
    byte[] buffer = w.GetBytes();
    
    // example send buffer
    Magic.Send(buffer);
  • Reader
    using Byter;
    
    // example receive buffer
    byte[] buffer = Magic.Receive();
    
    // create instance
    Reader r = new()
        
    // read data
    
    string noticeInEnglish = r.Read<string>(); // Powered by ALEC1O
    string noticeInChinese = r.Read<string>(Encoding.UTF32); // 由 ALEC1O 提供支持
    int myInt = r.Read<int>(); // 1.000.000
    char myChar = r.Read<char>(); // 'A'
    long myLong = r.Read<long>(); // -100.0000.000
    byte[] myBytes = r.Read<byte[]>(); // [0, 1, 2, 3]
    
    // Float(1|2|3) only available in version 2
    Float2 myFloat2 = r.Read<Float2>(); // [x: -100F] [y: 300F]
    Float3 myFloat3 = r.Read<Float3>(); // [x: -100F] [y: 300F] [z: 600F]
    Float4 myFloat4 = r.Read<Float4>(); // [x: -100F] [y: 300F] [z: 600F] [w: 900F]
    
    if (r.Sucess)
    {
        // sucess on read all data
    }
    else
    {
        // one or more data isn't found when deserialize. Might ignore this buffer!
    }
  • Dynamic Read Technical
    var r = new Reader(...);
    
    var topic = r.Read<string>(Encoding.ASCII);
    
    if(!r.Sucess) return; // ignore this 
    
    if (topic == "login")
    {
        string username = r.Read<string>(Encoding.UTF32);
        string password = r.Read<string>(Encoding.ASCII);
        
        if (!r.Sucess) return; // ignore this
        // login user...
    }
    else if(topic == "get user address")
    {
        ulong userId  = r.Read<ulong>();
        string token = r.Read<string>(Encoding.ASCII);
        
        if (!r.Sucess) return; // ignore this
        // get user adress...
    }
    ...
    else
    {
        // ignore this. (Topic not found)
    }
v3.x.x
📄 Primitive

Constructor

  • () : Primitive
    Create instance with empty internal buffer
  • (byte[] buffer) : Primitive
    Create instance using (Byte[]) as internal buffer

Proprieties

  • Position : int
    Return internal reading index/position.
  • IsValid : bool
    Return (true) if data was read successful. otherwise (false)
  • Add : IPrimitiveAdd
    Object used to (read/get) content from internal (Primitive) buffer
  • Get : IPrimitiveGet
    Object used to (write/add) content in internal (Primitive) buffer

Methods

  • GetBytes() : byte[]
    Return buffer from (Primitive) instance as byte[])
  • GetList() : List<byte>
    Return buffer from (Primitive) instance as List<byte>
  • Reset(): void
    Clear internal buffer from (Primitive) instance
📄 IPrimitiveAdd

Methods

  • Bool(bool value) void
    (Write/Add) element typeof(bool) in internal buffer
  • Byte(byte value) void
    (Write/Add) element typeof(byte) in internal buffer
  • SByte(sbyte value) void
    (Write/Add) element typeof(sbyte) in internal buffer
  • Char(char value) void
    (Write/Add) element typeof(char) in internal buffer
  • Short(short value) void
    (Write/Add) element typeof(short) in internal buffer
  • UShort(ushort value) void
    (Write/Add) element typeof(ushort) in internal buffer
  • Int(int value) void
    (Write/Add) element typeof(int) in internal buffer
  • UInt(uint value) void
    (Write/Add) element typeof(uint) in internal buffer
  • Float(float value) void
    (Write/Add) element typeof(float) in internal buffer
  • Enum<T>(T value) void
    (Write/Add) element typeof(enum) in internal buffer
  • Long(long value) void
    (Write/Add) element typeof(long) in internal buffer
  • ULong(ulong value) void
    (Write/Add) element typeof(ulong) in internal buffer
  • Double(double value) void
    (Write/Add) element typeof(double) in internal buffer
  • DateTime(DateTime value) void
    (Write/Add) element typeof(DateTime) in internal buffer
  • Decimal(decimal value) void
    (Write/Add) element typeof(decimal) in internal buffer
  • String(string value) void
    (Write/Add) element typeof(string) in internal buffer
  • Class<T>(T value) void
    (Write/Add) element typeof(T) in internal buffer
  • Struct<T>(T value) void
    (Write/Add) element typeof(T) in internal buffer
  • Array<T>(T value) void
    (Write/Add) element typeof(T[]) in internal buffer
  • List<T>(List<T> value) void
    (Write/Add) element typeof(List) in internal buffer
  • BigInteger(BigInteger value) void
    (Write/Add) element typeof(BigInteger) in internal buffer
  • Bytes(byte[] value) void
    (Write/Add) element typeof(byte[]) in internal buffer
📄 IPrimitiveGet

Methods

  • Bool() bool
    (Read/Get) element typeof(bool) from internal buffer
  • Byte() byte
    (Read/Get) element typeof(byte) from internal buffer
  • SByte() sbyte
    (Read/Get) element typeof(sbyte) from internal buffer
  • Char() char
    (Read/Get) element typeof(char) from internal buffer
  • Short() short
    (Read/Get) element typeof(short) from internal buffer
  • UShort() ushort
    (Read/Get) element typeof(ushort) from internal buffer
  • Int() int
    (Read/Get) element typeof(int) from internal buffer
  • UInt() uint
    (Read/Get) element typeof(uint) from internal buffer
  • Float() float
    (Read/Get) element typeof(float) from internal buffer
  • Enum<T>() T
    (Read/Get) element typeof(enum) from internal buffer
  • Long() long
    (Read/Get) element typeof(long) from internal buffer
  • ULong() ulong
    (Read/Get) element typeof(ulong) from internal buffer
  • Double() double
    (Read/Get) element typeof(double) from internal buffer
  • DateTime() DateTime
    (Read/Get) element typeof(DateTime) from internal buffer
  • Decimal() decimal
    (Read/Get) element typeof(decimal) from internal buffer
  • String() string
    (Read/Get) element typeof(string) from internal buffer
  • Class<T> () T
    (Read/Get) element typeof(T) from internal buffer
  • Struct<T>() T
    (Read/Get) element typeof(T) from internal buffer
  • Array<T>() T[]
    (Read/Get) element typeof(T[]) from internal buffer
  • List<T>() List<T>
    (Read/Get) element typeof(List<T) from in internal buffer
  • BigInteger() BigInteger
    (Read/Get) element typeof(BigInteger) from internal buffer
  • Bytes() byte[]
    (Read/Get) element typeof(byte[]) from internal buffer
📄 Example
  • Add Element
    using Byter;
    
    Primitive primitive = new();
    
    // write elements
    
    primitive.Add.Class(myCharacterInfoClass);
    primitive.Add.Array(myCharacterArray);
    primitive.Add.List(myLogList);
    primitive.Add.Struct(myDeviceStruct);
    primitive.Add.DateTime(DateTime.UtcNow);
    primitive.Add.Enum(MyEnum.Option1);
    primitive.Add.Bytes(myImageBuffer);
    
    // send buffer
    
    byte[] buffer = primitive.GetBytes();
    Magic.Send(buffer); // EXAMPLE!
  • Get Element
    using Byter;
    
    // receive bugger
    
    byte[] buffer = Magic.Receive(); // EXAMPLE!
    
    Primitive primitive = new(buffer);
    
    // read elements
    
    var myCharacterInfoClass = primitive.Get.Class<CharacterInfoClass>();
    var myCharacterArray = primitive.Get.Array<Character>();
    var myLogList = primitive.Get.List<string>();
    var myDeviceStruct = primitive.Get.Struct<DeviceStruct>();
    var myTime = primitive.Get.DateTime();
    var myEnum = primitive.Get.Enum<MyEnum>();
    var myImageBuffer = primitive.Get.Bytes();
    
    if (primitive.IsValid)
    {
        // sucess on read all data
    }
    else
    {
        // one or more data isn't found when deserialize. Might ignore this buffer!
    }
  • Dynamic Read Technical
    Primitive primitive = new(...);
    
    var topic = primitive.Get.String();
    
    if(!primitive.IsValid) return; // ignore this 
    
    if (topic == "login")
    {
        var loginInfo = primitive.Get.Class<LoginInfo>();
        
        if (!primitive.IsValid) return; // ignore this
        // login user...
    }
    else if (topic == "get user address")
    {
        var getUserAddressInfo = primitive.Get.Class<GetUserAddressInfo>();
        
        if (!primitive.IsValid) return; // ignore this
        // get user adress...
    }
    ...
    else
    {
        // ignore this. (Topic not found)
    }

Overhead (supported types list)

Byter overhead information

Type Primitive (overhead + size = total) Writer/Reader (overhead + size = total)
Bool ✔️ (1 + 1 = 2 bytes) ✔️ (2 + 1 = 3 bytes)
Byte ✔️ (1 + 1 = 2 bytes) ✔️ (2 + 1 = 3 bytes)
SByte ✔️ (1 + 2 = 2 bytes) 🚫
Char ✔️ (1 + 2 = 3 bytes) ✔️ (2 + 2 = 4 bytes)
Short ✔️ (1 + 2 = 3 bytes) ✔️ (2 + 2 = 4 bytes)
UShort ✔️ (1 + 2 = 3 bytes) ✔️ (2 + 2 = 4 bytes)
Int ✔️ (1 + 4 = 5 bytes) ✔️ (2 + 4 = 6 bytes)
UInt ✔️ (1 + 4 = 5 bytes) ✔️ (2 + 4 = 6 bytes)
Float ✔️ (1 + 4 = 5 bytes) ✔️ (2 + 4 = 6 bytes)
Enum ✔️ (1 + 4 = 5 bytes) 🚫
Long ✔️ (1 + 8 = 9 bytes) ✔️ (2 + 8 = 10 bytes)
ULong ✔️ (1 + 8 = 9 bytes) ✔️ (2 + 8 = 10 bytes)
Double ✔️ (1 + 8 = 9 bytes) ✔️ (2 + 8 = 10 bytes)
DateTime ✔️ (1 + 8 = 9 bytes) 🚫
Decimal ✔️ (1 + 16 = 17 bytes) 🚫
String ✔️ (5 + ? = +5 bytes) *UTF8 ✔️ (6 + ? = +6 bytes)
Class ✔️ (2 + 0 = 2 bytes) 🚫
Struct ✔️ (2 + 0 = 2 bytes) 🚫
Array ✔️ (3 + ? = +3 bytes) *Max. 65535 🚫
List ✔️ (3 + ? = +3 bytes) *Max. 65535 🚫
BigInteger ✔️ (3 + ? = +3 bytes) 🚫
Bytes ✔️ (5 + ? = +5 bytes) *Max. 4.294.967.295 *(~4billions) ✔️ (6 + ? = +6 bytes) *Max. 2.147.483.647 *(~2billions)

Encoding Extension

using Byter;
  • Convert string to byte[]

    // using global encoding (*UTF8)
    byte[] username  = "@alec1o".GetBytes(); 
    
    // using UNICODE (*UTF16) encoding
    byte[] message = "Hello 👋 World 🌎".GetBytes(Encoding.Unicode); 
    
    // using UTF32 encoding
    string secreatWord = "I'm not human, I'm  a concept.";
    byte[] secreat = secreatWord.GetBytes(Encoding.UTF32);
  • Convert byte[] to string

    // using global encoding (*UTF8)
    string username  = new byte[] { ... }.GetString(); 
    
    // using UNICODE (*UTF16) encoding
    string message = new byte[] { ... }.GetString(Encoding.Unicode); 
    
    // using UTF32 encoding
    byte[] secreat = new byte[] { ... };
    string secreatWord = secreat.GetString(Encoding.UTF32);
  • Concat bytes (byte[])

    byte[] part1 = [ 1, 1, 1 ];
    byte[] part2 = [ 4, 4, 4 ];
    
    /* 
        ..          ..      ..      ..      ..      ..      ..      ..      ..      .. 
        
        Concat part1 and part2 
        
        ..          ..      ..      ..      ..      ..      ..      ..      ..      .. 
        
        --- `byte[].Concat` Used concat bytes normally.
        --- `byte[].ConcatInverse` Used concat bytes inversed (first is last and last is first).
        --- `byte[].Concat(invert: true|false, ....)` Generic way to concat `Inversed` and `Normal` direction.
        
        ..          ..      ..      ..      ..      ..      ..      ..      ..      .. 
    */
    
    
    // Normal >>>
    byte[] normal = part1.Concat(part2); //... [ 1, 1, 1, 4, 4, 4 ]
    byte[] normal = part2.Concat(invert: true, part1); //... [ 1, 1, 1, 4, 4, 4 ]
    byte[] normal = byte[].Concat(invert: true, part2, part1); //... [ 1, 1, 1, 4, 4, 4 ]
    byte[] normal = byte[].Concat(invert: false, part1, part2); //... [ 1, 1, 1, 4, 4, 4 ]  
    
    // Inversed <<<
    byte[] inversed = part1.ConcatInverse(part2); //... [ 4, 4, 4, 1, 1, 1 ]
    byte[] inversed = part2.Concat(invert: false, part1); //... [ 4, 4, 4, 1, 1, 1 ]
    byte[] inversed = byte[].Concat(invert: false, part2, part1); //... [ 4, 4, 4, 1, 1, 1 ]
    byte[] inversed = byte[].Concat(invert: true, part1, part2); //... [ 4, 4, 4, 1, 1, 1 ]
  • Capitalize string

    string name = "alECio furanZE".ToCapitalize();
    # Alecio Furanze
    
    string title = "i'M noT humAn";
    title = title.ToCapitalize();
    # I'm Not Human
  • UpperCase string

    string name = "alECio furanZE".ToUpperCase();
    # ALECIO FURANZE
    
    string title = "i'M noT humAn";
    title = title.ToUpperCase();
    # I'M NOT HUMAN
  • LowerCase string

    string name = "ALEciO FUraNZE".ToLowerCase();
    # alecio furanze
    
    string title = "i'M Not huMAN";
    title = title.ToLowerCase();
    # i'm not human
Legacy Docs (v1.x.x - v2.x.x)

Byter

Byter is a bytes serializer. It can serialize and deserialize from primitive type.

Byter is very stable, super easy to learn, extremely fast and inexpensive (2 bytes or sizeof(char) of overhead per data written) and 100% written in C# and it's FREE!




Install

  • Nuget SEE HERE

    .NET CLI
    dotnet add package Byter --version 2.0
    Git submodule
    See how add as git submodule? See on bottom of this docs



Usage

Namespace

using Byter

Types

[
    `byte`,
    `bool`,
    `byte[]`,
    `short`,
    `ushort`,
    `int`,
    `uint`,
    `long`,
    `ulong`,
    `float`,
    `double`,
    `char`,
    `string`,
    `Float2`(Vector2),
    `Float3`(Vector3),
    `Float4`(Vector4 / Quaternion),
]

Writer

Constructor

_ = new Writer();                          // Create default instance
_ = new Writer(new Writer());              // Create instance and copy from existing Writer
_ = new Writer(ref new Writer());          // Create instance and copy from existing Writer (using ref)

Proprietary

  • Length

    using Byter;
    
    var w = new Writer();
    
    // Get lenght of buffer
    int lenght = w.Length; 

Methods

  • Write(dynamic value)

    using Byter;
    
    // Create writer instance;
    using var w = new Writer();
    
    // Write string
    w.Write("KEZERO");
    
    // Write char
    w.Write('K');
    
    // Write Float3 (Vector3)
    w.Write(new Float3(10F, 10F, 10F));
    
    // Get bytes (buffer)
    byte[] buffer = w.GetBytes();
    
    // Get byte list (buffer)
    List<byte> bytes = w.GetList();
  • GetBytes()

    using Byter;
    
    var w = new Writer();
    
    // Return buffer on <Writer> instance 
    byte[] buffer = w.GetBytes();
  • GetList()

    using Byter;
    
    var w = new Writer();
    
    // Return buffer on <Writer> instance as byte list 
    List<byte> bytes = w.GetList();
  • Clear()

    using Byter;
    
    var w = new Writer();
    w.Write((int)1000);
    w.Write((float)100f);
    
    // Clear internal buffer and reset internal index
    w.Clear();

Reader

Constructor

_ = new Reader(new Writer());               // Create instance and copy buffer from existing Writer
_ = new Reader(ref new Writer());           // Create instance and copy buffer from existing Writer (ref Writer)
_ = new Reader(new byte[] { 1, 1, 1, 1 });  // Create instance from buffer (bytes (byte[]))

Proprietary

  • Length

    using Byter;
    
    byte[] buffer = ...;
    var r = new Reader(buffer);
    
    // Get lenght of buffer
    int lenght = r.Length; 
  • Position

    using Byter;
    
    byte[] buffer = ...;
    var r = new Reader(buffer);
    
    // return current index of readed buffer
    int position = r.Position; 
  • Success

    using Byter;
    
    byte[] buffer = ...;
    var r = new Reader(buffer);
    string name = r.Read<string>();
    int age = r.Read<int>();
    
    // return true if not exist problem on read values.
    // return false when have any error on read values;
    bool success = r.Success; 
    • WARNING
      Internally, before data is written a prefix is added in front of it, so when reading it always compares the prefix of the (data type) you want to read with the strings in the read buffer. if the prefixes do not match then o ( Reader. Success = False), eg. If you write an (int) and try to read a float (Reader.Success = False) because the prefix of an (int) is different from that of a (float), it is recommended to read all the data and at the end check the success, if it is (Reader.Success = False) then one or more data is corrupt. This means that Writer and Reader add dipping to your write and read data.

Methods

  • Read<T>() Read<string>(Encoding encoding);

    using Byter;
    
    byte[] buffer = ...;
    
    // Create reader instance
    using r = new Reader(buffer);
    
    string name = r.Read<string>();
    char firstLatter = r.Read<char>();
    Float3 position = r.Read<Float3>();
    
    // Check if is success
    if (r.Success)
    {
        Console.WriteLine($"Name: {name}");
        Console.WriteLine($"First Latter: {firstLatter}");
        Console.WriteLine($"Position: {position}");
    }
    else
    {
        Console.WriteLine("Error on get data");
    }
  • Seek(int position);

    using Byter;
    
    using var w = new Writer();
    w.Write("KEZERO");
    w.Write((int) 1024);
    
    using var r = new Reader(ref w);
    
    string name = r.Read<string>(); // name: KEZERO
    int age = r.Read<int>(); // age: 1024
    
    // Move index (Reader.Position) for target value "MIN VALUE IS 0";
    r.Seek(10); // move current index for read for 10 (when start read using .Read<Type> will start read on 10 index from buffer);
    
    // Reset internal position
    r.Seek(0);
    
    string name2 = r.Read<string>(); // name: KEZERO (because the start index of this string on buffer is 0)
    int age2 = r.Read<int>(); age: 1024;
    
    // NEED READ LAST INT
    r.Seek(r.Position - sizeof(int) + sizeof(char) /* int size is 4 + char size is 2. The 2 bytes is overhead of protocol */);    
    int age3 = r.Read<int>(); age: 1024 (because i return 4bytes before old current value)




Sample

using Byter;

// writing
Writer writer = new();

writer.Write(1000); // index
writer.Write("{JSON}"); // content
writer.Write(new byte[]{ 1, 1, 1, 1 }); // image

// geting buffer
byte[] buffer = writer.GetBytes();
writer.Dispose(); // Destroy Writer

// reading
Reader reader = new(buffer);

int index = reader.Read<int>();
string json = reader.Read<string>();
byte[] image = reader.Read<byte[]>();

// Check error
if (!reader.Success) // IS FALSE
{
    Console.WriteLine("*** ERROR ****");
    return;
}

// Check success
Console.WriteLine("*** SUCCESS ****");      

// Output
Console.WriteLine($"Index: {index}");           // output: 1000
Console.WriteLine($"JSON : {json }");           // output: JSON
Console.WriteLine($"Image: {image.Length}");    // output: 4
Console.WriteLine($"Status: {reader.Success}"); // output: True

// Making error
float delay = reader.Read<float>();
                                                                                            /*
WARNING:                
if you reverse the reading order or try to read more data than added (Reader.Succes = False),
Remembering does not return exception when trying to read data that does not exist it just
returns the default construction, and (Reader.Success) will be assigned (False)             */

if (reader.Success)  // IS FALSE, THE IS NOT WRITED IN BUFFER
    Console.WriteLine($"Delay: {delay}");
else                // IS TRUE, THE DELAY NOT EXIST
    Console.WriteLine($"Delay not exist");

// Output of status
Console.WriteLine($"Status: {reader.Success}"); // output: False

reader.Dispose(); // Destroy Reader




Install using git submodule

# Install - recommend a stable branch e.g. "1.x" or use a fork repository, --depth clone last sources
git submodule add --name byter --depth 1 --branch main "https://github.com/alec1o/byter" vendor/byter

# Rebuilding - Download repository and link it in file location, must add this step in dotnet.yaml if using
git submodule update --init

# Update submodule - Update and load new repository updates
git submodule update --remote

# PATH
# |__ vendor
# |   |__ byter
# |      |__ src
# |        |__ Byter.csproj
# |
# |__ app
# |   |__ app.csproj
# |
# |__ app.sln
# |__ .git
# |__ .gitignore
# |__ .gitmodules

# .NET link on .sln
cd <PATH>
dotnet sln add vendor/byter/src/Byter.csproj

# .NET link on .csproj
cd app/
dotnet add reference ../vendor/byter/src/Byter.csproj

# Rebuild dependencies to be linked in the project
dotnet restore