1

I have an extension method that converts an array of numeric type A to an array of numeric type B

public static T2[] ConvertTo<T1, T2>(this T1[] buffer)
{
    var bufferNumBytes = buffer.Length * Marshal.SizeOf(default(T1));
    var targetElementNumBytes = Marshal.SizeOf(default(T2));
    if (bufferNumBytes % targetElementNumBytes != 0)
    {
        throw new ArgumentException($"Array must have multiple of {targetElementNumBytes} bytes, has {bufferNumBytes} bytes instead", nameof(buffer));
    }
    var res = new T2[bufferNumBytes / targetElementNumBytes];
    Buffer.BlockCopy(buffer, 0, res, 0, bufferNumBytes);
    return res;
}

I can call it like

    var b = new ushort[] { 1, 256 };
    var us = b.ConvertTo<ushort, byte>();

    Assert.That(us[0], Is.EqualTo(1));
    Assert.That(us[1], Is.EqualTo(0));
    Assert.That(us[2], Is.EqualTo(0));
    Assert.That(us[3], Is.EqualTo(1));

However, that T1 paramter seems redundant, but I don´t know how to get rid of it. One workaround would be more generic extension methods

public static T1[] ConvertTo<T1>(this byte[] buffer)
{
    return buffer.ConvertTo<byte, T1>();
}

public static T1[] ConvertTo<T1>(this sbyte[] buffer)
{
    return buffer.ConvertTo<sbyte, T1>();
}

public static T1[] ConvertTo<T1>(this ushort[] buffer)
{
    return buffer.ConvertTo<ushort, T1>();
}
...

But I would prefer a simpler approach with just one parameter (target type) and the compiler infering the type of buffer by itself.

downforme
  • 371
  • 2
  • 15
  • 2
    Type inference is all or nothing. There's no way to have it infer `T1` unless it can also infer `T2`; and it cannot infer `T2`. – Damien_The_Unbeliever Sep 27 '21 at 13:58
  • Possibly related: [Why doesn't C# infer my generic types?](https://stackoverflow.com/questions/8511066/why-doesnt-c-sharp-infer-my-generic-types) – D M Sep 27 '21 at 14:01
  • Does this answer your question? [Inferring only one type with multiple generic types](https://stackoverflow.com/questions/60652090/inferring-only-one-type-with-multiple-generic-types) – D M Sep 27 '21 at 14:02
  • @Damien_The_Unbeliever It could if they are willing to add a throw away `T2` parameter, but that is a hacky way to get around this issue. – juharr Sep 27 '21 at 14:06
  • @juharr At that point you might as well use Java and it's broken implementation of generics - `buffer.ConvertTo(Byte.class)` :D – RB. Sep 27 '21 at 14:11

1 Answers1

5

You can get most of what you are looking for by using a fluent interface:

void Main()
{
    var b = new ushort[] { 1, 256 };

    // We now use a fluent interface to do the conversion - T1 is
    // now inferred.
    var us = b.Convert().To<byte>();
    
    us.Dump();
}

public static class Extensions
{
    public static IConverter Convert<T1>(this T1[] buffer)
    {
        return new Converter<T1>(buffer);
    }
}

public interface IConverter
{
    T2[] To<T2>();
}

public class Converter<T1> : IConverter
{
    private readonly T1[] _buffer;
    public Converter(T1[] buffer)
    {
        _buffer = buffer ?? throw new ArgumentNullException();
    }
    
    public T2[] To<T2>()
    {
        var bufferNumBytes = _buffer.Length * Marshal.SizeOf(default(T1));
        var targetElementNumBytes = Marshal.SizeOf(default(T2));
        if (bufferNumBytes % targetElementNumBytes != 0)
        {
            throw new ArgumentException($"Array must have multiple of {targetElementNumBytes} bytes, has {bufferNumBytes} bytes instead", nameof(_buffer));
        }
        var res = new T2[bufferNumBytes / targetElementNumBytes];
        Buffer.BlockCopy(_buffer, 0, res, 0, bufferNumBytes);
        return res;
    }
}

The downsides are you are allocating extra memory for the Converter class, and you end up calling two methods - but discoverability should be basically as good as your original example, which is what I think is really motivating your question.

RB.
  • 36,301
  • 12
  • 91
  • 131
  • that´s sexy, but wouldn´t it make more sense to name the extension method public static IConverter Converter(this T1[] buffer)? After all, if I simply call b.Convert() it doesn´t actually convert anthing, but gives me a Converter Interface. – downforme Sep 27 '21 at 14:38
  • @downforme With fluent interfaces, you typically design them to be very human-readable - in this case, the names chosen allow the code to be read as an English description of the intent. – RB. Sep 27 '21 at 14:54
  • I like it so much :) I spent so much time of my life creating fluent things like this – Manitra Andriamitondra May 19 '23 at 07:39