Costruisci un array di byte da diversi tipi di dati per l'invio su una rete

Aug 22 2020

Lo scopo del seguente codice è generare un array di byte che rappresenta alcuni dati definiti dall'utente. Questo array di byte verrà successivamente utilizzato per l'invio su una rete.

WelcomePacketWriterè una sottoclasse concreta di PacketWriter. Le sottoclassi di PacketWriterpossono utilizzare metodi forniti da PacketWriterper scrivere nell'elenco di byte (metodi come WriteInt, WriteStringe InsertShort). L'utente può generare e ottenere un array di byte da una PacketWritersottoclasse chiamando il GetBytesmetodo che chiama il metodo astratto GenerateBufferContent(che dovrebbe apportare modifiche al campo buffer) e converte il campo buffer da un elenco a un array.

La mia preoccupazione principale è che in PacketWriter.cs ci sia molto codice duplicato per la gestione di diversi tipi di dati ( GetBytes(short _value), GetBytes(int _value), GetBytes(float _value), ecc...). Ho pensato di utilizzare un metodo generico per GetBytes, tuttavia, poiché il BitConverter.GetBytesmetodo non è generico, non posso passare un tipo generico. L'uso di un'istruzione switch basata sul tipo nel metodo generico comporterà comunque un codice duplicato e potrebbe non essere chiaro quali tipi di dati possono essere passati senza generare un errore.

PacketWriter.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace test
{
    public abstract class PacketWriter
    {
        private List<byte> buffer;

        internal PacketWriter()
        {
            buffer = new List<byte>();
        }

        public byte[] GetBytes()
        {
            GenerateBufferContent();
            byte[] bytes = buffer.ToArray();
            buffer.Clear();
            return bytes;
        }

        protected abstract void GenerateBufferContent();

        protected void Write(byte[] _value)
        {
            buffer.AddRange(_value);
        }
        protected void Insert(byte[] _value)
        {
            buffer.InsertRange(0, _value);
        }

        protected void WriteByte(byte _value)
        {
            Write(new byte[1] { _value });
        }

        protected void WriteShort(short _value)
        {
            Write(GetBytes(_value));
        }

        protected void WriteInt(int _value)
        {
            Write(GetBytes(_value));
        }

        protected void WriteFloat(float _value)
        {
            Write(GetBytes(_value));
        }

        protected void WriteBool(bool _value)
        {
            Write(GetBytes(_value));
        }

        protected void InsertByte(byte _value)
        {
            Insert(new byte[1] { _value });
        }

        protected void WriteString(string _value)
        {
            WriteInt(_value.Length);
            Write(GetBytes(_value));
        }

        protected void InsertShort(short _value)
        {
            Insert(GetBytes(_value));
        }

        protected void InsertInt(int _value)
        {
            Insert(GetBytes(_value));
        }

        protected void InsertFloat(float _value)
        {
            Insert(GetBytes(_value));
        }

        protected void InsertBool(bool _value)
        {
            Insert(GetBytes(_value));
        }

        protected void InsertString(string _value)
        {
            Insert(GetBytes(_value));
            InsertInt(_value.Length);
        }

        private byte[] GetBytes(short _value)
        {
            return BitConverter.GetBytes(_value);
        }
        private byte[] GetBytes(int _value)
        {
            return BitConverter.GetBytes(_value);
        }
        private byte[] GetBytes(float _value)
        {
            return BitConverter.GetBytes(_value);
        }
        private byte[] GetBytes(bool _value)
        {
            return BitConverter.GetBytes(_value);
        }
        private byte[] GetBytes(string _value)
        {
            return Encoding.ASCII.GetBytes(_value);
        }
    }
}

WelcomePacket.cs

namespace ConsoleAppTest
{
    public class WelcomePacketWriter : PacketWriter
    {
        private string message;
        private short clientId;

        public WelcomePacketWriter(short _clientId, string _message)
        {
            message = _message;
            clientId = _clientId;
        }
        protected override void GenerateBufferContent()
        {
            WriteShort(clientId);
            WriteString(message);
        }
    }
}

Risposte

5 ndogac Aug 22 2020 at 16:38

Puoi usare Unsafela classe e renderla generica.

Ecco il codice:

using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;

namespace Test
{
    public abstract class PacketWriter
    {
        private readonly List<byte> _buffer;

        internal PacketWriter()
        {
            _buffer = new List<byte>();
        }

        public byte[] GetBytes()
        {
            GenerateBufferContent();
            byte[] bytes = _buffer.ToArray();
            _buffer.Clear();
            return bytes;
        }

        protected abstract void GenerateBufferContent();

        // Edited, Added Encoding Parameter
        protected void Write(string value, Encoding encoding)
        {
            var bytes = encoding.GetBytes(value);

            _buffer.AddRange(bytes);
        }

        protected void Write<T>(T value) where T : unmanaged
        {
            var bytes = GetBytes(value);

            _buffer.AddRange(bytes);
        }

        // Edited, Added Encoding Parameter
        protected void Insert(string value, Encoding encoding)
        {
            var bytes = encoding.GetBytes(value);

            _buffer.InsertRange(0, bytes);
        }

        protected void Insert<T>(T value) where T : unmanaged
        {
            var bytes = GetBytes(value);

            _buffer.InsertRange(0, bytes);
        }

        private byte[] GetBytes<T>(T value) where T : unmanaged
        {
            var bytes = new byte[Unsafe.SizeOf<T>()];

            Unsafe.As<byte, T>(ref bytes[0]) = value;

            return bytes;
        }
    }
}

Nota importante qui è che non puoi usare Stringcon questo metodo generico. Devi usare Encoding.UTF8.GetByteso Encoding.ASCII.GetBytes.

Credo che dobbiamo aggiungere un vincolo where T : structo where T : unmanagedper impedire a qualcuno di utilizzare Unsafe.SizeOf<T>()e Unsafe.As<TFrom, TTo>(ref byte source)con un tipo di riferimento gestito.

null Aug 23 2020 at 03:40

Trattare i tipi come un oggetto e quindi serializzarli con BinaryFormatter sarebbe una soluzione adeguata.

Object è il tipo di base di tutti i tipi C#, anche dei tipi di valore.

Come questo?

class Program
{
    static void Main(string[] args)
    {
        PacketWriter writer = new PacketWriter();
        writer.Write("Hello, World");
        writer.Write(1);
        writer.Write(true);

        byte[] bytes = writer.GetBytes();

    }
}

class PacketWriter
{
    private List<byte> buffer;

    internal PacketWriter()
    {
        buffer = new List<byte>();
    }

    public byte[] GetBytes()
    {
        return this.buffer.ToArray();
    }

    public void Write(object value)
    {
        this.Write(this.GetBytes(value));
    }

    protected void Write(byte[] value)
    {
        buffer.AddRange(value);
    }

    private byte[] GetBytes(object value)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter serialiser = new BinaryFormatter();
            serialiser.Serialize(stream, value);

            return stream.ToArray();
        }
    }
}