0

I'm parsing binary files. As such I have written something akin to the following:

public T Parse<T>(BinaryReader reader)
{
    if (typeof(T) == typeof(byte))
        return reader.ReadByte();
    else if (typeof(T) == typeof(int))
        return reader.ReadInt32();
    // ...
}

However this doesn't compile:

Cannot implicitly convert type '...' to 'T'

It would be very convenient for me to do this.

How do I return any primitive from a generic method?

Cobra_Fast
  • 15,671
  • 8
  • 57
  • 102
  • "However this doesn't compile." means you're getting a compilation error. What is it? – Zohar Peled May 26 '20 at 21:03
  • 4
    You'd have to cast like `return (byte)(object)reader.ReadByte()` but it makes me wonder what the caller is getting out of this by calling `Parse(reader)` instead of `read.ReadByte()`. You are going to cause boxing of any value types returned from this method. – Mike Zboray May 26 '20 at 21:04
  • @ZoharPeled Error added. – Cobra_Fast May 26 '20 at 21:05
  • @MikeZboray I'm going to have to assemble some structs and I was thinking I could just use recursion on `Parse()` for each field when that happens. – Cobra_Fast May 26 '20 at 21:06
  • @MikeZboray Casting like that actually doesn't compile either. Same error. – Cobra_Fast May 26 '20 at 21:10
  • 3
    @Cobra_Fast Oh sorry. I meant `return (T)(object)reader.ReadByte()`. – Mike Zboray May 26 '20 at 21:14

2 Answers2

1

This is fairly common with generic code that checks runtime types. You have to upcast to object then downcast to the generic T. This is because the compiler doesn't 'know' the byte is directly convertible to T even it is identical to T because of the type check.

public T Parse<T>(BinaryReader reader)
{
    if (typeof(T) == typeof(byte))
        return (T)(object)reader.ReadByte();
    else if (typeof(T) == typeof(int))
        return (T)(object)reader.ReadInt32();
    // all returns will need similar casts
}

The downside to this is that the cast to object causes a boxing operation and adds to GC pressure so it could be bad for performance sensitive code.

Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
  • ... Which is a part of the reason why [type checks and generics don't mix](https://stackoverflow.com/a/59519715/3094533)... – Zohar Peled May 27 '20 at 05:07
1

I don't know why do you need this functionality(and if it is worth the hassle, taking in account that you are losing compile time safety) but you can try this to remove the unnecessary allocations in the solution by @Mike Zboray:

public static T Parse<T>(BinaryReader reader)
{
    return ReaderProvider<T>.ReaderFunc(reader);
}

class ReaderProvider<T> 
{
    public static readonly Func<BinaryReader, T> ReaderFunc;
    static ReaderProvider()
    {
        MethodInfo mi ;
        if(typeof(T) == typeof(byte))
        {
            mi = typeof(BinaryReader).GetMethod(nameof(BinaryReader.ReadByte));
        }
        else if(typeof(T) == typeof(int))
        {
            mi = typeof(BinaryReader).GetMethod(nameof(BinaryReader.ReadInt32));
        }
        // ....
        else
        {
            throw new ArgumentOutOfRangeException($"{typeof(T).FullName}"); 
        }
        var p = Expression.Parameter(typeof(BinaryReader));
        ReaderFunc = Expression.Lambda<Func<BinaryReader, T>>(Expression.Call(p, mi), p).Compile();
    }
}

Example usage:

var ms = new MemoryStream();
ms.Write(new byte[] { 1, 2, 0, 0, 0 }, 0, 5);
ms.Seek(0, SeekOrigin.Begin);
var x = new BinaryReader(ms);

Console.WriteLine(Parse<byte>(x));
Console.WriteLine(Parse<int>(x));

Also I highly recommend to stick with BinaryReader.ReadX methods. If this is not suitable for then benchmark performance of both implementations.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132