diff --git a/EonaCat.FastWriter/EonaCat.FastWriter.Testers/EonaCat.FastWriter.Testers.csproj b/EonaCat.FastWriter/EonaCat.FastWriter.Testers/EonaCat.FastWriter.Testers.csproj
new file mode 100644
index 0000000..d6a9457
--- /dev/null
+++ b/EonaCat.FastWriter/EonaCat.FastWriter.Testers/EonaCat.FastWriter.Testers.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/EonaCat.FastWriter/EonaCat.FastWriter.Testers/Program.cs b/EonaCat.FastWriter/EonaCat.FastWriter.Testers/Program.cs
new file mode 100644
index 0000000..48b71b3
--- /dev/null
+++ b/EonaCat.FastWriter/EonaCat.FastWriter.Testers/Program.cs
@@ -0,0 +1,122 @@
+using EonaCat.FastWriter;
+
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+internal class Program
+{
+ private static async Task Main(string[] args)
+ {
+ // Test serialization for simple objects
+ var user = new User
+ {
+ Id = 1,
+ Name = "Alice",
+ IsAdmin = true,
+ Home = new Address { Street = "123 Main St", City = "Wonderland" }
+ };
+
+ byte[] data1 = FastSerializer.Serialize(user);
+ User clone = FastSerializer.Deserialize(data1);
+
+ Console.WriteLine($"User clone: {clone.Name}, Admin={clone.IsAdmin}, City={clone.Home.City}");
+
+ // Test big collections
+ var numbers = new List();
+ for (int i = 0; i < 100_000; i++)
+ {
+ numbers.Add(i);
+ }
+
+ byte[] data2 = FastSerializer.Serialize(numbers);
+ var copy1 = FastSerializer.Deserialize>(data2);
+
+ Console.WriteLine($"Big list count: {copy1.Count}, First={copy1[0]}, Last={copy1[^1]}");
+
+ // Test array of strings
+ string[] names = { "A", "B", "C", "D", "E" };
+ var data3 = FastSerializer.Serialize(names);
+ var copy2 = FastSerializer.Deserialize(data3);
+
+ Console.WriteLine($"String array length: {copy2.Length}, Second={copy2[1]}");
+
+ // Test nested collections
+ var users = new List();
+ for (int i = 0; i < 10; i++)
+ {
+ users.Add(new User
+ {
+ Id = i,
+ Name = $"User{i}",
+ IsAdmin = i % 2 == 0,
+ Home = new Address { Street = $"Street {i}", City = $"City {i}" }
+ });
+ }
+
+ byte[] data4 = FastSerializer.Serialize(users);
+ var copy3 = FastSerializer.Deserialize>(data4);
+
+ Console.WriteLine($"Nested list count: {copy3.Count}, Last user city: {copy3[^1].Home.City}");
+
+ // Test dictionary
+ var dict = new Dictionary();
+ for (int i = 0; i < 5; i++)
+ {
+ dict[$"key{i}"] = new User { Id = i, Name = $"DUser{i}", Home = new Address { Street = $"S{i}", City = $"C{i}" } };
+ }
+
+ byte[] data5 = FastSerializer.Serialize(dict);
+ var copy4 = FastSerializer.Deserialize>(data5);
+
+ Console.WriteLine($"Dictionary count: {copy4.Count}, key2 user name: {copy4["key2"].Name}");
+
+ // Server and Client test with messages
+ var server = new Server(12345);
+
+ server.MessageReceived += async (msg, client) =>
+ {
+ Console.WriteLine($"Server received: {msg.Text}, Number={msg.Number}");
+ await server.SendAsync(new Message { Text = "Ack: " + msg.Text, Number = msg.Number }, client);
+ };
+
+ _ = server.StartAsync();
+
+ var client = new Client("127.0.0.1", 12345);
+
+ client.MessageReceived += async msg =>
+ {
+ Console.WriteLine($"Client received: {msg.Text}, Number={msg.Number}");
+ await client.SendAsync(new Message { Text = $"Got your {msg.Number}", Number = 999 });
+ };
+
+ await client.ConnectAsync();
+ await client.SendAsync(new Message { Text = "Hello Server", Number = 123 });
+
+ // Send nested objects and big collections through server
+ await client.SendAsync(new Message { Text = $"Nested test: {copy3[0].Name}", Number = copy3.Count });
+ await client.SendAsync(new Message { Text = $"Big list last value: {copy1[^1]}", Number = copy1.Count });
+
+ Console.WriteLine("Press any key to exit...");
+ Console.ReadKey();
+ }
+}
+
+public class Address
+{
+ public string Street { get; set; } = "";
+ public string City { get; set; } = "";
+}
+
+public class User
+{
+ public int Id { get; set; }
+ public string Name { get; set; } = "";
+ public bool IsAdmin { get; set; }
+ public Address Home { get; set; } = new Address();
+}
+
+public class Message
+{
+ public string Text { get; set; } = "";
+ public int Number { get; set; }
+}
\ No newline at end of file
diff --git a/EonaCat.FastWriter/EonaCat.FastWriter.slnx b/EonaCat.FastWriter/EonaCat.FastWriter.slnx
new file mode 100644
index 0000000..0d76c64
--- /dev/null
+++ b/EonaCat.FastWriter/EonaCat.FastWriter.slnx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/EonaCat.FastWriter/EonaCat.FastWriter/Client.cs b/EonaCat.FastWriter/EonaCat.FastWriter/Client.cs
new file mode 100644
index 0000000..3f75c5c
--- /dev/null
+++ b/EonaCat.FastWriter/EonaCat.FastWriter/Client.cs
@@ -0,0 +1,199 @@
+namespace EonaCat.FastWriter;
+
+using System;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+public class Client
+{
+ private readonly string _host;
+ private readonly int _port;
+ private TcpClient? _client;
+ private NetworkStream? _stream;
+ private readonly bool _autoReconnect;
+ private readonly int _reconnectDelayMs;
+ private readonly bool _useRaw;
+ private CancellationTokenSource _cts = new();
+
+ public string Nickname { get; private set; } = Guid.NewGuid().ToString();
+
+ // Events
+ public event Action? MessageReceived;
+ public event Action? RawMessageReceived;
+ public event Action? Connected;
+ public event Action? Disconnected;
+
+ public Client(string host, int port, bool autoReconnect = true, int reconnectDelayMs = 3000, string? nickname = null, bool useRaw = false)
+ {
+ _host = host;
+ _port = port;
+ _autoReconnect = autoReconnect;
+ _reconnectDelayMs = reconnectDelayMs;
+ _useRaw = useRaw; // store raw mode
+ if (!string.IsNullOrEmpty(nickname))
+ {
+ Nickname = nickname;
+ }
+ }
+
+ public async Task ConnectAsync()
+ {
+ _cts = new CancellationTokenSource();
+ await ConnectInternalAsync(_cts.Token);
+ }
+
+ private async Task ConnectInternalAsync(CancellationToken token)
+ {
+ while (!token.IsCancellationRequested)
+ {
+ try
+ {
+ _client = new TcpClient();
+ await _client.ConnectAsync(_host, _port);
+ _stream = _client.GetStream();
+ Connected?.Invoke();
+
+ // Start receiving messages automatically in the chosen mode
+ _ = ReceiveLoopAsync(token);
+ return;
+ }
+ catch
+ {
+ Disconnected?.Invoke();
+ if (!_autoReconnect)
+ {
+ throw;
+ }
+
+ await Task.Delay(_reconnectDelayMs, token);
+ }
+ }
+ }
+
+ public async Task SendAsync(T msg)
+ {
+ if (_useRaw)
+ {
+ throw new InvalidOperationException("Client is in raw mode; use SendRawAsync instead.");
+ }
+
+ if (_stream == null || _client == null || !_client.Connected)
+ {
+ if (_autoReconnect)
+ {
+ await ConnectInternalAsync(_cts.Token);
+ }
+ else
+ {
+ throw new InvalidOperationException("Client not connected");
+ }
+ }
+
+ byte[] data = FastSerializer.Serialize(msg);
+ byte[] lenBytes = BitConverter.GetBytes(data.Length);
+
+ await _stream.WriteAsync(lenBytes, 0, lenBytes.Length);
+ await _stream.WriteAsync(data, 0, data.Length);
+ }
+
+ public async Task SendRawAsync(byte[] data)
+ {
+ if (!_useRaw)
+ {
+ throw new InvalidOperationException("Client is not in raw mode; use SendAsync instead.");
+ }
+
+ if (_stream == null || _client == null || !_client.Connected)
+ {
+ if (_autoReconnect)
+ {
+ await ConnectInternalAsync(_cts.Token);
+ }
+ else
+ {
+ throw new InvalidOperationException("Client not connected");
+ }
+ }
+
+ await _stream.WriteAsync(data, 0, data.Length);
+ }
+
+ private async Task ReceiveLoopAsync(CancellationToken token)
+ {
+ if (_stream == null)
+ {
+ return;
+ }
+
+ var stream = _stream;
+
+ try
+ {
+ while (!token.IsCancellationRequested)
+ {
+ if (_useRaw)
+ {
+ var buffer = new byte[4096];
+ int read = await stream.ReadAsync(buffer, 0, buffer.Length, token);
+ if (read == 0)
+ {
+ break; // disconnected
+ }
+
+ byte[] received = new byte[read];
+ Array.Copy(buffer, 0, received, 0, read);
+
+ RawMessageReceived?.Invoke(received);
+ }
+ else
+ {
+ byte[] lenBytes = new byte[4];
+ int read = await stream.ReadAsync(lenBytes, 0, 4, token);
+ if (read == 0)
+ {
+ break;
+ }
+
+ int length = BitConverter.ToInt32(lenBytes, 0);
+ byte[] data = new byte[length];
+ int totalRead = 0;
+ while (totalRead < length)
+ {
+ totalRead += await stream.ReadAsync(data, totalRead, length - totalRead, token);
+ }
+
+ var msg = FastSerializer.Deserialize(data);
+ MessageReceived?.Invoke(msg);
+ }
+ }
+ }
+ catch
+ {
+ // connection lost
+ }
+ finally
+ {
+ Disconnected?.Invoke();
+ _client?.Close();
+ _stream = null;
+
+ if (_autoReconnect && !token.IsCancellationRequested)
+ {
+ await ConnectInternalAsync(token);
+ }
+ }
+ }
+
+ public void Disconnect()
+ {
+ _cts.Cancel();
+ _stream?.Close();
+ _client?.Close();
+ }
+
+ public void SetNickname(string nickname) => Nickname = nickname;
+}
diff --git a/EonaCat.FastWriter/EonaCat.FastWriter/EonaCat.FastWriter.csproj b/EonaCat.FastWriter/EonaCat.FastWriter/EonaCat.FastWriter.csproj
new file mode 100644
index 0000000..b98d3ac
--- /dev/null
+++ b/EonaCat.FastWriter/EonaCat.FastWriter/EonaCat.FastWriter.csproj
@@ -0,0 +1,37 @@
+
+
+
+ netstandard2.1
+ enable
+ latest
+ True
+ EonaCat.FastWriter
+ EonaCat.FastWriter
+ EonaCat (Jeroen Saey)
+ EonaCat
+ EonaCat (Jeroen Saey)
+ https://git.saey.me/EonaCat/EonaCat.FastWriter
+ https://git.saey.me/EonaCat/EonaCat.FastWriter
+ readme.md
+ icon.png
+ git
+ fast;writer;serialize;deserialize;EonaCat;Jeroen;Saey;binary;Speed;performance
+ LICENSE
+
+
+
+
+ True
+ \
+
+
+ True
+ \
+
+
+ True
+ \
+
+
+
+
diff --git a/EonaCat.FastWriter/EonaCat.FastWriter/FastReader.cs b/EonaCat.FastWriter/EonaCat.FastWriter/FastReader.cs
new file mode 100644
index 0000000..c44b155
--- /dev/null
+++ b/EonaCat.FastWriter/EonaCat.FastWriter/FastReader.cs
@@ -0,0 +1,44 @@
+using System.Text;
+
+namespace EonaCat.FastWriter;
+
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+
+internal ref struct FastReader
+{
+ private readonly ReadOnlySpan _span;
+ private int _pos;
+ public FastReader(ReadOnlySpan span) { _span = span; _pos = 0; }
+
+ public byte ReadByte() => _span[_pos++];
+ public int ReadInt() { int result = 0, shift = 0; byte b; do { b = ReadByte(); result |= (b & 0x7F) << shift; shift += 7; } while ((b & 0x80) != 0); return result; }
+ public long ReadInt64() { long result = 0; int shift = 0; byte b; do { b = ReadByte(); result |= ((long)(b & 0x7F)) << shift; shift += 7; } while ((b & 0x80) != 0); return result; }
+ public string? ReadString()
+ {
+ int len = ReadInt();
+ if (len < 0)
+ {
+ return null;
+ }
+
+ var s = Encoding.UTF8.GetString(_span.Slice(_pos, len));
+ _pos += len;
+ return s;
+ }
+
+ // Primitive support
+ public bool ReadBool() => ReadByte() != 0;
+ public byte ReadByteValue() => ReadByte();
+ public sbyte ReadSByte() => (sbyte)ReadByte();
+ public short ReadShort() => (short)ReadInt();
+ public ushort ReadUShort() => (ushort)ReadInt();
+ public uint ReadUInt() => (uint)ReadInt();
+ public long ReadLong() => ReadInt64();
+ public ulong ReadULong() => (ulong)ReadInt64();
+ public float ReadFloat() => BitConverter.Int32BitsToSingle(ReadInt());
+ public double ReadDouble() => BitConverter.Int64BitsToDouble(ReadInt64());
+ public char ReadChar() => (char)ReadInt();
+}
diff --git a/EonaCat.FastWriter/EonaCat.FastWriter/FastSerializer.cs b/EonaCat.FastWriter/EonaCat.FastWriter/FastSerializer.cs
new file mode 100644
index 0000000..82584ae
--- /dev/null
+++ b/EonaCat.FastWriter/EonaCat.FastWriter/FastSerializer.cs
@@ -0,0 +1,26 @@
+using System.Buffers;
+
+namespace EonaCat.FastWriter;
+
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using EonaCat.FastWriter.Models;
+using System;
+
+public static class FastSerializer
+{
+ public static byte[] Serialize(T value)
+ {
+ var buffer = new ArrayBufferWriter();
+ var writer = new FastWriter(buffer);
+ Dispatcher.Write(ref writer, value, typeof(T));
+ return buffer.WrittenSpan.ToArray();
+ }
+
+ public static T Deserialize(ReadOnlySpan data)
+ {
+ var reader = new FastReader(data);
+ return (T)Dispatcher.Read(ref reader, typeof(T))!;
+ }
+}
diff --git a/EonaCat.FastWriter/EonaCat.FastWriter/FastWriter.cs b/EonaCat.FastWriter/EonaCat.FastWriter/FastWriter.cs
new file mode 100644
index 0000000..9839663
--- /dev/null
+++ b/EonaCat.FastWriter/EonaCat.FastWriter/FastWriter.cs
@@ -0,0 +1,42 @@
+using System.Buffers;
+using System.Text;
+
+namespace EonaCat.FastWriter;
+
+using System;
+
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+internal ref struct FastWriter
+{
+ private readonly IBufferWriter _buffer;
+ public FastWriter(IBufferWriter buffer) => _buffer = buffer;
+
+ public void WriteByte(byte v) { var s = _buffer.GetSpan(1); s[0] = v; _buffer.Advance(1); }
+ public void WriteInt(int value) { uint v = (uint)value; while (v >= 0x80) { WriteByte((byte)(v | 0x80)); v >>= 7; } WriteByte((byte)v); }
+ public void WriteInt64(long value) { ulong v = (ulong)value; while (v >= 0x80) { WriteByte((byte)(v | 0x80)); v >>= 7; } WriteByte((byte)v); }
+
+ public void WriteString(string? value)
+ {
+ if (value == null) { WriteInt(-1); return; }
+ int byteCount = Encoding.UTF8.GetByteCount(value);
+ WriteInt(byteCount);
+ var span = _buffer.GetSpan(byteCount);
+ Encoding.UTF8.GetBytes(value.AsSpan(), span);
+ _buffer.Advance(byteCount);
+ }
+
+ // Primitive support
+ public void WriteBool(bool v) => WriteByte(v ? (byte)1 : (byte)0);
+ public void WriteByteValue(byte v) => WriteByte(v);
+ public void WriteSByte(sbyte v) => WriteByte((byte)v);
+ public void WriteShort(short v) => WriteInt(v);
+ public void WriteUShort(ushort v) => WriteInt(v);
+ public void WriteUInt(uint v) => WriteInt((int)v);
+ public void WriteLong(long v) => WriteInt64(v);
+ public void WriteULong(ulong v) => WriteInt64((long)v);
+ public void WriteFloat(float v) => WriteInt(BitConverter.SingleToInt32Bits(v));
+ public void WriteDouble(double v) => WriteInt64(BitConverter.DoubleToInt64Bits(v));
+ public void WriteChar(char v) => WriteInt(v);
+}
diff --git a/EonaCat.FastWriter/EonaCat.FastWriter/Models/Dispatcher.cs b/EonaCat.FastWriter/EonaCat.FastWriter/Models/Dispatcher.cs
new file mode 100644
index 0000000..04be8ed
--- /dev/null
+++ b/EonaCat.FastWriter/EonaCat.FastWriter/Models/Dispatcher.cs
@@ -0,0 +1,212 @@
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace EonaCat.FastWriter.Models;
+
+using System;
+
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+internal static class Dispatcher
+{
+ private static readonly ConcurrentDictionary Models = new();
+
+ public static void Write(ref FastWriter w, object? value, Type type)
+ {
+ if (value == null) { w.WriteByte(0); return; }
+ w.WriteByte(1);
+
+ if (TryWritePrimitive(ref w, value, type))
+ {
+ return;
+ }
+
+ if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string))
+ {
+ WriteEnumerable(ref w, (IEnumerable)value, type);
+ return;
+ }
+
+ WriteObject(ref w, value, type);
+ }
+
+ public static object? Read(ref FastReader r, Type type)
+ {
+ if (r.ReadByte() == 0)
+ {
+ return null;
+ }
+
+ if (TryReadPrimitive(ref r, type, out var value))
+ {
+ return value;
+ }
+
+ if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string))
+ {
+ return ReadEnumerable(ref r, type);
+ }
+
+ return ReadObject(ref r, type);
+ }
+
+ private static bool TryWritePrimitive(ref FastWriter w, object value, Type type)
+ {
+ if (type == typeof(bool)) { w.WriteBool((bool)value); return true; }
+ if (type == typeof(byte)) { w.WriteByteValue((byte)value); return true; }
+ if (type == typeof(sbyte)) { w.WriteSByte((sbyte)value); return true; }
+ if (type == typeof(short)) { w.WriteShort((short)value); return true; }
+ if (type == typeof(ushort)) { w.WriteUShort((ushort)value); return true; }
+ if (type == typeof(int)) { w.WriteInt((int)value); return true; }
+ if (type == typeof(uint)) { w.WriteUInt((uint)value); return true; }
+ if (type == typeof(long)) { w.WriteLong((long)value); return true; }
+ if (type == typeof(ulong)) { w.WriteULong((ulong)value); return true; }
+ if (type == typeof(float)) { w.WriteFloat((float)value); return true; }
+ if (type == typeof(double)) { w.WriteDouble((double)value); return true; }
+ if (type == typeof(char)) { w.WriteChar((char)value); return true; }
+ if (type == typeof(string)) { w.WriteString((string)value); return true; }
+ if (type.IsEnum) { w.WriteInt(Convert.ToInt32(value)); return true; }
+
+ return false;
+ }
+
+
+ private static bool TryReadPrimitive(ref FastReader r, Type type, out object? value)
+ {
+ value = type switch
+ {
+ Type t when t == typeof(bool) => r.ReadBool(),
+ Type t when t == typeof(byte) => r.ReadByteValue(),
+ Type t when t == typeof(sbyte) => r.ReadSByte(),
+ Type t when t == typeof(short) => r.ReadShort(),
+ Type t when t == typeof(ushort) => r.ReadUShort(),
+ Type t when t == typeof(int) => r.ReadInt(),
+ Type t when t == typeof(uint) => r.ReadUInt(),
+ Type t when t == typeof(long) => r.ReadLong(),
+ Type t when t == typeof(ulong) => r.ReadULong(),
+ Type t when t == typeof(float) => r.ReadFloat(),
+ Type t when t == typeof(double) => r.ReadDouble(),
+ Type t when t == typeof(char) => r.ReadChar(),
+ Type t when t == typeof(string) => r.ReadString(),
+ Type t when t.IsEnum => Enum.ToObject(t, r.ReadInt()),
+ _ => null
+ };
+ return value != null;
+ }
+
+ private static void WriteObject(ref FastWriter w, object obj, Type type)
+ {
+ var model = Models.GetOrAdd(type, t => new TypeModel(t));
+ w.WriteInt(model.Properties.Length);
+ foreach (var p in model.Properties)
+ {
+ int id = TypeModel.GetFieldId(p);
+ w.WriteInt(id);
+ var value = model.Accessors[id].Getter(obj);
+ Write(ref w, value, p.PropertyType);
+ }
+ }
+
+ private static object ReadObject(ref FastReader r, Type type)
+ {
+ var model = Models.GetOrAdd(type, t => new TypeModel(t));
+ var obj = model.ObjectCreator();
+ int count = r.ReadInt();
+ for (int i = 0; i < count; i++)
+ {
+ int id = r.ReadInt();
+ if (model.Accessors.TryGetValue(id, out var accessor))
+ {
+ accessor.Setter(obj, Read(ref r, accessor.PropertyType));
+ }
+ else
+ {
+ Skip(ref r);
+ }
+ }
+ return obj;
+ }
+
+ private static void WriteEnumerable(ref FastWriter w, IEnumerable value, Type type)
+ {
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
+ {
+ var dictType = type;
+ var keyType = dictType.GetGenericArguments()[0];
+ var valueType = dictType.GetGenericArguments()[1];
+ var dict = (System.Collections.IDictionary)value;
+
+ w.WriteInt(dict.Count);
+ foreach (System.Collections.DictionaryEntry entry in dict)
+ {
+ Write(ref w, entry.Key, keyType);
+ Write(ref w, entry.Value, valueType);
+ }
+ return;
+ }
+
+ // normal IEnumerable handling
+ var list = value.Cast