How to use generic classes with fields of another generic class of the same generic type?

33 Views Asked by At

I want to implement a set of classes, that can be used both with UInt16 and UInt32 type parameters.

The first one represents the header of a telegram, that can be used both with UInt32 and UInt64 data types.

Any non essential fields have been ommitted!

Currently I am working with .NetFramework 4.7.2. and VS 2017. Upgrading to another framework may be an option, but could break other things, like dependencies...

public class Header<T> // where T : struct, IConvertible
{
    public Header(T off, T len)
    {
        Offset = off;
        LenData = len;
    }
    public T Offset;
    public T LenData;
    public byte[] ToByteArray()
    {
        byte[] bTemp = new byte[2 * Marshal.SizeOf(typeof(T))];
        if (Offset is UInt16)
        {
            Array.ConstrainedCopy(System.BitConverter.GetBytes(Convert.ToUInt16(Offset)), 0, bTemp, 0, Marshal.SizeOf(typeof(T)));
            Array.ConstrainedCopy(System.BitConverter.GetBytes(Convert.ToUInt16(LenData)), 0, bTemp, Marshal.SizeOf(typeof(T)), Marshal.SizeOf(typeof(T)));
        }
        else if (Offset is UInt32)
        {
            Array.ConstrainedCopy(System.BitConverter.GetBytes(Convert.ToUInt16(Offset)), 0, bTemp, 0, Marshal.SizeOf(typeof(T)));
            Array.ConstrainedCopy(System.BitConverter.GetBytes(Convert.ToUInt16(LenData)), 0, bTemp, Marshal.SizeOf(typeof(T)), Marshal.SizeOf(typeof(T)));
        }
        else
        {
            throw new NotSupportedException("Type " + Offset.GetType() + "not supported");
        }

        return bTemp;
    }
}

This class gives me a byte array for the telegram header (2 + 2 bytes for T = UInt16 and 4 + 4 for T = UInt32).

Now I need another class, which uses the header class to build the byte array for the telegram:

class Telegram<T>
{
    public Header<T> header;
    public Telegram(T offset, byte[] bData)
    {
        header = new Header<T>(offset,bData.Length)
    }
}

On its own, the class works as expected:

static void Main(string[] args)
{
    byte[] bData = new byte[16];
    // This call works
    Header<UInt16> ui16Header = new Header<ushort>(12345, (ushort)bData.Length);
    byte[] b0 = ui16Header.ToByteArray();

b0 contains the desired telegram content...

Then i want to create two telegrams (for both supported data types:

    // These calls don't work (because of compile error, or
    // because of a thrown exception - see below)
    Telegram<UInt16> telegram16 = new Telegram<ushort>(7000, bData);
    Telegram<UInt32> telegram32 = new Telegram<uint>(70000, bData);
}

The constructor of the class Telegram from above does not compile (cant convert int to T).

After changing the constructor as follows, it can be compiled:

    public Telegram(T offset, byte[] bData)
    {
        object ot = bData.Length;
        T len = (T)ot;
        header = new Header<T>(offset, len);
    }

But this leads to a runtime exception.

Can this problem be solved? I know, that I could simply write two classes that contain all type specific methods themselves, but only the fields in the header are either UInt16 or UInt32

Or would it be the easier way to work with inheritance and interfaces?

1

There are 1 best solutions below

4
Charlieface On

In .NET 7+, you can use the new INumber etc interfaces.

This allows you to cast to/from byte arrays, and to/from other integer or number types.

public class Header<T> where T : struct, IBinaryInteger<T>
{
    public Header(T off, T len)
    {
        Offset = off;
        LenData = len;
    }

    public T Offset { get; set; }
    public T LenData { get; set; }

    public byte[] ToByteArray()
    {
        var size = Offset.GetByteCount();
        byte[] bTemp = new byte[2 * size];
        Offset.WriteLittleEndian(bTemp);
        LenData.WriteLittleEndian(bTemp, size);
        return bTemp;
    }
}

public class Telegram<T> where T : struct, IBinaryInteger<T>
{
    public Header<T> Header { get; set; }
    public byte[] Data { get; set; }

    public Telegram(T offset, byte[] bData)
    {
        Header = new Header<T>(offset, T.CreateTruncating(bData.Length))
        Data = bData;
    }
}