Here's a question about EndianIO classes I see

Looking at sources like PackageIO, it isn’t uncommon for me to see something like this:


private BinaryReader reader;

/* ... some other stuff... */

public string ReadAsciiString(int length)
{
     return Encoding.ASCII.GetString(reader.ReadBytes(length));
}

public string ReadUnicodeString(int length, EndianType endianType)
{
     byte[] buffer = reader.ReadBytes(length);
     return endianType == EndianType.Little ? 
          Encoding.Unicode.GetString(buffer) :
          Encoding.BigEndianUnicode.GetString(buffer);
}

But what I don’t understand about this code is if you declare the BinaryReader without specifying an Encoding, it will default to UTF8Encoding. If that’s the case, how can you call ReadAsciiString() and ReadUnicodeString() and expect to get what you want for both cases? What I mean is, in both cases, the BinaryReader is reading the same amount of bytes (if you passed 10 in for the length in both cases, for example) but correct me if I’m wrong, isn’t the Unicode encoding of a character two bytes where as an ASCII encoding is only one byte?

The confusion is very clear in my head but I don’t feel like I’m explaining it very well.

EDIT: After doing some reading, I think I’ve found an answer for myself.

The Encoding set in the BinaryReader class doesn’t matter because the ReadString functions of EndianIO read a byte array from the underlying BinaryReader, not a string. EndianIO converts the byte array into a string using Encoding.*.GetString(bytes). The ReadUnicodeString method in the code you referenced will read length/2 characters because Windows’ Unicode has 2 bytes per char. The parameters are definitely misleading because I’d naturally prefer to pass it the length of the string in characters, not in bytes.

If you want, I can send you my EndianIO class (not the same as the ones online).

Sure I’d be interested to see it!

Here you go:


using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace DaringDev.IO
{
    public enum EndianType
    {
        Big,
        Little,
        Native
    }

    public sealed class EndianIO
    {
        private EndianType _endianness;
        private bool _requiresReverse;

        private readonly Stream _stream;
        private readonly bool _publicMemoryStream;
        private readonly byte[] _buffer = new byte[8];

        public EndianIO(Stream stream, EndianType endianType = EndianType.Native)
        {
            _stream = stream;
            Endianness = endianType;
        }

        public EndianIO(EndianType endianType = EndianType.Native)
            : this(new MemoryStream(), endianType)
        {
            _publicMemoryStream = true;
        }

        public EndianIO(int maxSize, EndianType endianType = EndianType.Native)
            : this(new MemoryStream(maxSize), endianType)
        {
            _publicMemoryStream = true;
        }

        public EndianIO(byte[] buffer, EndianType endianType = EndianType.Native)
            : this(new MemoryStream(buffer), endianType)
        {

        }

        public EndianIO(string fileName, EndianType endianType = EndianType.Native, FileMode fileMode = FileMode.Open,
            FileAccess fileAccess = FileAccess.ReadWrite, FileShare fileShare = FileShare.Read, int bufferSize = 4096, bool isAsync = false)
            : this(new FileStream(fileName, fileMode, fileAccess, fileShare, bufferSize, isAsync), endianType)
        {

        }

        public Stream BaseStream
        {
            get { return _stream; }
        }

        public EndianType Endianness
        {
            get { return _endianness; }
            set
            {
                _endianness = value;

                switch (value)
                {
                    case EndianType.Native:
                        _requiresReverse = false;
                        break;
                    case EndianType.Little:
                        _requiresReverse = !BitConverter.IsLittleEndian;
                        break;
                    case EndianType.Big:
                        _requiresReverse = BitConverter.IsLittleEndian;
                        break;
                }
            }
        }

        public bool Eof
        {
            get { return Position == Length; }
        }

        public long Length
        {
            get
            {
                return _stream.Length;
            }
        }

        public long Position
        {
            get { return _stream.Position; }
            set { _stream.Position = value; }
        }

        public void Seek(long position, SeekOrigin origin)
        {
            _stream.Seek(position, origin);
        }

        public void Flush()
        {
            _stream.Flush();
        }

        public void SetLength(long value)
        {
            _stream.SetLength(value);
        }

        public Stream GetStream()
        {
            return _stream;
        }

        public void Close()
        {
            _stream.Close();
        }

        public byte[] ToArray(bool preferInternalReference = false)
        {
            var ms = _stream as MemoryStream;

            if (ms == null)
            {
                throw new NotSupportedException("ToArray() is only supported by memory streams.");
            }

            return preferInternalReference && _publicMemoryStream ? ms.GetBuffer() : ms.ToArray();
        }

        public int Read(byte[] buffer, int offset, int count)
        {
            return _stream.Read(buffer, offset, count);
        }

        public async Task<int> ReadAsync(byte[] buffer, int offset, int count)
        {
            return await _stream.ReadAsync(buffer, offset, count);
        }

        public byte[] ReadByteArray(int count)
        {
            var buffer = new byte[count];
            Read(buffer, 0, count);
            return buffer;
        }

        public async Task<byte[]> ReadByteArrayAsync(int count)
        {
            var buffer = new byte[count];
            await ReadAsync(buffer, 0, count);
            return buffer;
        }

        public byte[] ReadByteArray(long count)
        {
            var buffer = new byte[count];
            Read(buffer, 0, buffer.Length);
            return buffer;
        }

        public byte[] ReadToEnd()
        {
            using (var ms = new MemoryStream(81920))
            {
                _stream.CopyTo(ms);
                return ms.ToArray();
            }
        }

        public async Task<byte[]> ReadToEndAsync()
        {
            using (var ms = new MemoryStream(81920))
            {
                await _stream.CopyToAsync(ms);
                return ms.ToArray();
            }
        }

        public sbyte ReadSByte()
        {
            Read(_buffer, 0, 1);
            return (sbyte)_buffer[0];
        }

        public byte ReadByte()
        {
            Read(_buffer, 0, 1);
            return _buffer[0];
        }

        public bool ReadBoolean()
        {
            return ReadByte() != 0x00;
        }

        public ushort ReadUInt16()
        {
            return unchecked((ushort)ReadInt16());
        }

        public unsafe short ReadInt16()
        {
            Read(_buffer, 0, 2);

            if (_requiresReverse)
                return (short)(_buffer[1] | (_buffer[0] << :sunglasses:);

            fixed (byte* pbyte = &_buffer[0])
                return *((short*)pbyte);
        }

        public uint ReadUInt32()
        {
            return unchecked((uint)ReadInt32());
        }

        public unsafe int ReadInt32()
        {
            Read(_buffer, 0, 4);

            if (_requiresReverse)
                return _buffer[3] | (_buffer[2] << :sunglasses: | (_buffer[1] << 16) | (_buffer[0] << 24);

            fixed (byte* pbyte = &_buffer[0])
                return *((int*)pbyte);
        }

        public async Task<int> ReadInt32Async()
        {
            await ReadAsync(_buffer, 0, 4);

            if (_requiresReverse)
                return _buffer[3] | (_buffer[2] << :sunglasses: | (_buffer[1] << 16) | (_buffer[0] << 24);

            return BitConverter.ToInt32(_buffer, 0);
        }

        public ulong ReadUInt64()
        {
            return unchecked((ulong)ReadInt64());
        }

        public unsafe long ReadInt64()
        {
            Read(_buffer, 0, :sunglasses:;

            if (_requiresReverse)
            {
                var n1 = (_buffer[3] | (_buffer[2] << :sunglasses: | (_buffer[1] << 16) | (_buffer[0] << 24)) & 0xffffffff;
                var n2 = (_buffer[7] | (_buffer[6] << :sunglasses: | (_buffer[5] << 16) | (_buffer[4] << 24)) & 0xffffffff;

                return n2 | (n1 << 32);
            }

            fixed (byte* pbyte = &_buffer[0])
                return *((long*)pbyte);
        }

        public uint ReadUInt24()
        {
            Read(_buffer, 0, 3);

            if (!_requiresReverse)
                return (uint)(_buffer[0] | (_buffer[1] << :sunglasses: | (_buffer[2] << 16));

            return (uint)(_buffer[2] | (_buffer[1] << :sunglasses: | (_buffer[0] << 16));
        }

        public unsafe float ReadSingle()
        {
            var x = ReadInt32();

            return *(float*)&x;
        }

        public unsafe double ReadDouble()
        {
            var x = ReadInt64();

            return *((double*)&x);
        }

        public string ReadString(Encoding encoding, int lengthInBytes)
        {
            return encoding.GetString(ReadByteArray(lengthInBytes));
        }

        public async Task<string> ReadStringAsync(Encoding encoding, int lengthInBytes)
        {
            return encoding.GetString(await ReadByteArrayAsync(lengthInBytes));
        }

        public string ReadNullTerminatedString(Encoding encoding)
        {
            var sb = new StringBuilder();

            var io = new StreamReader(_stream, encoding, false, 16, true);

            int currentChar;
            while ((currentChar = io.Read()) != -1 && currentChar != 0)
            {
                sb.Append((char)currentChar);
            }

            io.Close();

            return sb.ToString();
        }

        public void Write(byte[] buffer, int offset, int count)
        {
            _stream.Write(buffer, offset, count);
        }

        public void Write(byte[] buffer)
        {
            Write(buffer, 0, buffer.Length);
        }

        public void Write(bool value)
        {
            const byte one = 1, zero = 0;
            Write(value ? one : zero);
        }

        public void Write(sbyte value)
        {
            Write(unchecked((byte)value));
        }

        public void Write(byte value)
        {
            _buffer[0] = value;
            Write(_buffer, 0, 1);
        }

        public void Write(short value)
        {
            Write(unchecked((ushort)value));
        }

        public void Write(ushort value)
        {
            if (_requiresReverse)
                value = (ushort)((value << :sunglasses: | (value >> :sunglasses:);

            Write(BitConverter.GetBytes(value));
        }

        public void Write(int value)
        {
            Write(unchecked((uint)value));
        }

        public void Write(uint value)
        {
            if (_requiresReverse)
                value = (value << 24) | (value >> 24) | ((value & 0xff00) << :sunglasses: | ((value >> :sunglasses: & 0xff00);

            Write(BitConverter.GetBytes(value));
        }

        public void Write(long value)
        {
            Write(unchecked((ulong)value));
        }

        public void Write(ulong value)
        {
            if (_requiresReverse)
            {
                value = (value << 56) | (value >> 56) |
                    ((value & 0xff00) << 40) | ((value >> 40) & 0xff00) |
                    ((value & 0xff0000) << 24) | ((value >> 24) & 0xff0000) |
                    ((value & 0xff000000) << :sunglasses: | ((value >> :sunglasses: & 0xff000000);
            }

            Write(BitConverter.GetBytes(value));
        }

        public unsafe void Write(float value)
        {
            Write(*((uint*)&value));
        }

        public unsafe void Write(double value)
        {
            Write(*((ulong*)&value));
        }

        public void WriteUInt24(uint value)
        {
            var buffer = BitConverter.GetBytes(value);

            Array.Resize(ref buffer, 3);

            if (_requiresReverse)
            {
                var t = buffer[0];
                buffer[0] = buffer[2];
                buffer[2] = t;
            }

            Write(buffer);
        }

        public void Write(string value, Encoding encoding)
        {
            if (value.Length != 0)
            {
                Write(encoding.GetBytes(value));
            }
        }

        public void Write(string value, Encoding encoding, int fixedLength)
        {
            if (fixedLength < 0)
            {
                throw new ArgumentOutOfRangeException("fixedLength");
            }

            if (fixedLength == 0 || value.Length == 0)
            {
                return;
            }

            if (value.Length > fixedLength)
            {
                value = value.Substring(0, fixedLength);
            }
            else if (value.Length < fixedLength)
            {
                value += new string('\0', fixedLength - value.Length);
            }

            Write(encoding.GetBytes(value));
        }

        private static readonly char[] NullCharacters = { '\0' };

        public void WriteNullTerminatedString(string value, Encoding encoding)
        {
            Write(encoding.GetBytes(value));
            Write(encoding.GetBytes(NullCharacters));
        }
    }
}

Nice code. I see no reason for you to use Aggressive Inlining, why do you need to go across application boundaries? comments would of pointed this out, if you know why you’re using AI instead of TPOO

Also, for what it’s worth, comments are the most important piece of a programmers arsenal, you have not used one anywhere, this is so disappointing. For example usage, its not useful, for someone learning they shouldn’t read the code … they should be reading higher level pseudocode which explains in simple terms what the code is doing low down. Someone learning is not going to always pick up on all details of the syntax and might miss what pseudocode would of pointed out.

I wrote that a couple of years ago when I thought I was being clever with aggressive inlining. Thanks, but I do not need comment criticism. I get enough of it, and nobody has to read my code anyway.

You were right. That is definitely unlike any EndianIO class I’ve ever found online. Thank you! If anything, this has opened my eyes to the idea that there are a number of ways to accomplish the same thing. And who else criticizes you commenting (or lack thereof)?

School, and people who see my code that work at companies where commenting is required. Don’t get me wrong, I think that commenting makes for more robust code, and it provides documentation to yourself and anybody who has to view it in the future. I just never got into the habit of doing it, and nothing bad has resulted from it yet.

Sent from my Nexus 6P using Tapatalk

With C# there are multiple ways to go about the same thing, depending on your situation and how much experience you’ve got. I rarely criticise people about how their code works, since it’s unique to everyone and how your brain likes to think, two people should rarely code the same thing exactly.

Comments on the other hand, some people think of them as “option extras” … they are vital for large scale applications and smaller. Comments ARE the most important piece of your work, I can not stress that enough, those who “don’t care” or “can’t be bothered” are not cut out to be programmers, its harsh but it’s true.

I work for a courier software company, any courier in the UK will (at some point in the process) use our software. I have been doing this for some time and I’ve never been criticised for criticising on someones comments before, this is the first and I found it very funny. I’d like to see any of your code being put through Code Review, QA, QC without any comments. I have never been to a development house where comments are not a requirement.

I sound like I am moaning. I am not. I’ve been a software developer for more years than Horizon has been out. I am trying to pass knowledge, not to stir up ****.

I disagree, a lot of people comment and then code gets changed and they don’t update comments or they add new ones but don’t explain that it changed the original one so now none of it useful. As he said he and one other person are the only one that ever look at it and they both understand it and since they don’t distribute the code then there isn’t any reason to do so. I find comments overly annoying when trying to read through projects as it turns from comments to a damn manual.

The end-goal of commenting is to document the code for other developers and to make sure you can come back to it in five years and still understand what is going on. If only you will be reading the code in the future, then shouldn’t you know how much commenting you need to accomplish this? I only comment lines that I know I will get confused about when I come back to them later on. If I were working with a team or if I knew my code was going to be reviewed/worked on by others in the future, THEN I would comment it completely.

All of my code would most-definitely fail a QA/QC process because of very limited commenting. That doesn’t inherently make it bad code.

Also, if a non-beginner can’t understand what’s going on in that EndianIO class without comments, then HE/SHE is not cut out to be a developer.

I do appreciate your opinion, and I hope we can agree that completely commenting the code isn’t necessary as long as you can understand it clearly and no other developers will be reading it. Aside from that situation, I agree that commenting is vital for the success of the product and the team.

You’re not meant to comment as you’re start to code it, since yes the code will be change and the comments will then become outdated. Comments are meant as a documentation method, or to make a co-developer laugh when he reads them (latter is less preferred but always funny). At my place of work for when we update code its just customary to add a simple update comment with an issue number (so it can be found in a issue logger, but not everyone uses an issue logger and every software house has its own style) prefixed with Software Change Request, as an example,


/*
* SCR #. Brief explanation of what it is
*/

Also, I don’t mean you have to code up every single line


// This moves startup from system 32 to trash
System.IO.File.Move(@"C:\System32\startup.bat", @"C:\Trash\startup.bat");
// Copy the file
System.IO.File.Copy(@"C:\Users\Default\Downloads\modded_startup.bat", @"C:\System32\startup.bat");
// Delete the file
System.IO.File.Delete(@"C:\Users\Default\Downloads\modded_startup.bat");

If someone did that, thats too much, you don’t need to comment every line, you can group lines of code that are similar/do a series of tasks together and comment it as a “block”, or type ‘///’ just before a function, Visual Studio will automatically generate a template block; with places for the description of the method, parameters, return.

Yes I agree with you there.

It doesn’t make it bad code, you’re correct. Its overall a good habit to get into, software houses love people who comment correctly, as yes lots of people can comment incorrectly lol, I’ve seen my fair share of **** comments, “//fill it with stuff” comes to mind a lot.

I agree with you there as well since its not a complicated task. You should have that basic level first.

I agree that commenting everything, like every line is not necessary, its more necessary to comment methods, complex operations, code thats not that easy to notice upon reading it through. Us developers are not well known for being good at naming methods, variables, classes and so I do like a comment on most of those :stuck_out_tongue: , I’ve done my fair share of crap variable names.

Since we agree now :smile:, we need to find something else to talk about. So that Amazon Prime FireTV stick advert with Jeremy Clarkson, is that in the US yet?

Haha alright. My commenting skills will shine one day :sunglasses:

As we’ve hijacked this thread… Does anybody have any questions about how the poorly-documented EndianIO class works?

Is this different from your current class?

The performance difference between using them vs the BitConverter class is a function call, so it’s a micro-optimization. If I wrote it again, I’d probably use the BitConverter class for brevety. No need to make any changes though because the pointers work correctly.

Hahaha I actually changed that question hoping you wouldn’t respond in the next 3 seconds. Do you still use this class?

Yep, we do. It’s in our DaringDev.Common assembly for any .NET projects that need it. Horizon v2 doesn’t use it though (it’s riddled with old code).