Reputation: 1052
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);
}
}
Upvotes: 6
Views: 3071
Reputation: 5357
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.
Upvotes: 1
Reputation: 1568
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.
Upvotes: 1
Reputation: 100047
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;
}
Upvotes: 0
Reputation: 38620
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.
Upvotes: 1
Reputation: 144206
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.
Upvotes: 1
Reputation: 79621
You'll need to use reflection to do that.
GetBytes
method group from the BitConverter
static type.TSource
.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.
Upvotes: 0
Reputation: 564891
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.
Upvotes: 8