6

Why does the following code not compile? How can I create a generic method which calls the appropriate "BitConverter.GetBytes" overload based on if the generic type is an "int", "bool", "char", etc.? More generally, how can I create a generic method which calls a non-generic method based on the generic parameter's type?

using System;

public class Test
{
    public static void Main()
    {
      var f = new Foo();
      f.GetBytes(10); // should call BitConverter.GetBytes(int);
      f.GetBytes(true); // should call BitConverter.GetBytes(bool);
      f.GetBytes('A'); // should call BitConverter.GetBytes(char);
    }
}

public class Foo
{
    public byte[] GetBytes <TSource> (TSource input)
    {
      BitConverter.GetBytes(input);
    }
}
Kevin Carrasco
  • 1,042
  • 7
  • 9
  • The answers below answered my question; that it is not really possible while keeping compile-time type safety, and without incurring run-time performance penalties. So, I just went with creating my own overloaded GetBytes methods for the input types which my method should accept, which then do some work and finally call the appropriate BitConverter.GetBytes. – Kevin Carrasco Apr 23 '14 at 18:53

7 Answers7

8

More generally, how can I create a generic method which calls a non-generic method based on the generic parameter's type?

In general, you can't, unless the method in question takes System.Object as a parameter. The problem is that the generic isn't constrained to just types that would be allowed by the method call arguments.

The closest you can do is to use runtime binding:

public byte[] GetBytes <TSource> (TSource input)
{
     dynamic obj = input;
     BitConverter.GetBytes(obj);
}

This pushes the method binding logic to runtime, and will throw if there isn't an appropriate method to call.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • You could add some constraints on TSource to help somewhat but it of course wouldn't be bullet proof without checking types which completely defeats the purpose of it being generic. – Kevin Apr 23 '14 at 18:13
  • 3
    @Kevin In this case, there's no way to add constraints that would work, since you'd need something like "is a bool or is a float or is an int", which isn't allowed. Overloads are required to support that type of "branching" in types. – Reed Copsey Apr 23 '14 at 18:14
  • I agree... I meant something like `where TSource: struct` which of course wouldn't be perfect but may be slightly better. – Kevin Apr 23 '14 at 18:18
  • @Kevin Still won't let it compile, though, unless the method you called was generic with that constraint. – Reed Copsey Apr 23 '14 at 18:19
  • 1
    I'm speaking purely of adding the constraint to your `GetBytes` implementation to limit the types it's allowed to be used with but keeping it's DLR usage so it will compile. – Kevin Apr 23 '14 at 18:22
  • @Kevin Ahh, okay - yes, that would help at least reduce the likelihood of it getting misused. I misunderstood. – Reed Copsey Apr 23 '14 at 18:23
1

The reason this doesn't work is that generic methods still resolve calls to methods made within them statically. Since TSource could be any type at all, it can only call a method on BitConverter which takes an object argument. Since none exists, the compilation fails.

The only way to get the behaviour you want to is to use dynamic:

public byte[] GetBytes <TSource> (TSource input)
{
    BitConverter.GetBytes((dynamic)input);
}

although the generic parameter is now redundant and you have no type safety.

In this situation you can either create a number of matching overloads e.g.

public byte[] GetBytes(bool b) { ... }
public byte[] GetBytes(int i) { ... }

or take a Func<T, byte[]> argument and wrap each BitConverter method you need e.g.

public void DoSomething<T>(T input, Func<T, byte[]> f)
{
    byte[] bytes = f(input);
    //handle bytes
}
DoSomething(true, BitConverter.GetBytes);

which may give you more flexibility.

Lee
  • 142,018
  • 20
  • 234
  • 287
1

Where the code is calling BitConverter.GetBytes, the type is TSource, so the call can't be statically bound by the compiler. You can work around this using dynamic invocation, meaning it'll compile fine and then get resolved at runtime:

…
public byte[] GetBytes(dynamic input)
{
    return BitConverter.GetBytes(input);
}

You will pay a performance penalty for using dynamic invocation and if no suitable method to call is available you will get a runtime exception.

Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
1

Given that there are "only" 10 overloads of BitConverter.GetBytes, it's not impossible to reflect them all explicitly like this:

public class Foo
{
    public byte[] GetBytes(bool input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(char input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(double input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(float input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(int input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(short input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(long input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(uint input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(ulong input) { return BitConverter.GetBytes(input); }
    public byte[] GetBytes(ushort input) { return BitConverter.GetBytes(input); }
}

It's not generic (which you had asked for), and isn't scalable to more complex examples, but if the numbers are small then it's an approach to consider.

ClickRick
  • 1,553
  • 2
  • 17
  • 37
1

If you are willing to take a performance hit, you can use reflection and an extension to object called GetBytes. example....

public static class Extensions
{
    #region Fields
    public static Type bcType;
    #endregion

    #region Constructor
    static Extensions()
    {
        bcType = typeof(BitConverter);
    }
    #endregion
    public static byte[] GetBytes(this object value)
    {
        Type typeObj = value.GetType();
        MethodInfo miGetBytes = bcType.GetMethod("GetBytes", new Type[] { typeObj });
        if (miGetBytes == null)
            throw new InvalidOperationException("Method: GetBytes on BitConverter does not have an overload accepting one paramter of type: " + typeObj.FullName);
        byte[] bytesRet = (byte[])miGetBytes.Invoke(null, new object[] { obj });
        return bytesRet;
    }
}

So GetBytes accepts an object. Then it get's it's type and tries to get the MethodInfo from BitConverter based on the type of object passed in. If it can't find an overload that accepts that type as a parameter it throws an InvalidOperation Exception. If it does, it calls it passing in the instance of obj as the value and returns the byte array.

E.g. Usage code,

//make sure the extensions namespace is defined where this code is run.
Console.WriteLine(((ushort)255).GetBytes().ToBase64());
Console.WriteLine(10.0.GetBytes().ToBase64());
Console.WriteLine(((int)2000000000).GetBytes().ToBase64());
Console.WriteLine(((short)128).GetBytes().ToBase64());
//Below causes an error
Console.WriteLine("cool".GetBytes().ToBase64()); //because BitConvert.GetBytes has no overload accepting an argument of type string.
Ryan Mann
  • 5,178
  • 32
  • 42
0

You'll need to use reflection to do that.

  1. Get the GetBytes method group from the BitConverter static type.
  2. Pull out the overload for which the first parameter has type TSource.
  3. Call that specific method via the Invoke method.

If you aren't familiar with some of this, I can expand the answer with code for those steps.

Edit: Or just use dynamic like others are suggesting and save yourself some work.

Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
0

Your code doesn't compile because the compiler can't verify that any type for TSource will be accepted by BitConverter.GetBytes(). You can check for each type individually and cast:

public byte[] GetBytes <TSource> (TSource input)
{
    var t = typeof(TSource);
    return    (t == typeof(int))  ? BitConverter.GetBytes((int) (object) input)
            : (t == typeof(bool)) ? BitConverter.GetBytes((bool)(object) input)
            : (t == typeof(char)) ? BitConverter.GetBytes((char)(object) input)
            : null;
}
Mark Cidade
  • 98,437
  • 31
  • 224
  • 236