I am writing code that interfaces with an external API that I cannot change:
public class ExternalAPI
{
public static void Read(byte[] buffer);
public static void Read(int[] buffer);
public static void Read(float[] buffer);
public static void Read(double[] buffer);
}
It's important that I call the correct overloaded method when reading data into buffer
, but once the data is read in, I will process it generically. My first pass at code that does that was:
public class Foo<T>
{
T[] buffer;
public void Stuff()
{
ExternalAPI.Foo(buffer);
}
}
C#, though, won't convert from T[]
to byte[]
. Is there a way to enumerate the types that T
can represent explicitly? I've tried using a where T :
clause, but there doesn't appear to be a way to say where T : {byte, int, float, double and nothing else ever}
?
Following the advice here: Generic constraint to match numeric types, I added constraints to the generic, and also added a generic method to my simulated API that takes an object
as its parameter
public class ExternalAPI
{
public static void Read(object buffer);
public static void Read(byte[] buffer);
public static void Read(int[] buffer);
public static void Read(double[] buffer);
}
public class Foo<T> where T: struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
{
T[] buffer;
public void Stuff()
{
ExternalAPI.Read(buffer);
}
}
This will compile and run happily, but the only method that is ever called is Foo(object buffer)
, even when T
is byte
. Is there a way to force methods of non-generic classes to use the most specific overload when the calling class is generic?
I've been in this situation before, and the only solution I've come up with is to test against the type of T
and call the appropriate function;
public class Foo<T>
{
T[] buffer;
public void Stuff()
{
var type = typeof(T);
if (type == typeof(int[]))
{
...
}
else if (type == typeof(double[]))
{
...
}
}
}
Is there a way to force methods of non-generic classes to use the most specific overload when the calling class is generic?
Normally, the overload resolution takes place at compile-time. Since the constraints on T
, namely where T: struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
, cannot point to any other overload than the one taking in object
, that's the overload getting called.
If you are willing to use dynamic
, the overload resolution will happen at run-time. Simply cast the argument to dynamic
, like this:
public void Stuff()
{
ExternalAPI.Read((dynamic)buffer);
}
The bad thing about this is that it's slower because overload resolution has to set in when your program runs. But if T
is one of byte
, int
, and so on, the corresponding overload with byte[]
, int[]
etc. will be called. If T
is something not supported, and if the object
overload does not exist in ExternalAPI
, this will fail at run-time, throwing an exception, but not until the Stuff
method runs.
Another solution is to equip your Foo<T>
class with a delegate to hold the correct method. It could be like:
T[] buffer;
readonly Action<T[]> readMethod;
public void Stuff()
{
readMethod(buffer);
}
//constructor
public Foo(Action<T[]> readMethod)
{
this.readMethod = readMethod;
}
But then people would have to instantiate your class like this:
new Foo<byte>(ExternalAPI.Read)
The right overload would be selected compile-time each place where people created instances of Foo<>
. You could add checks in you instance constructor that readMethod
is indeed a unicast delegate representing a method called Read
defined by typeof(ExternalAPI)
.
A third solution is to make the readMethod
field static
, and include a static constructor that initializes readMethod
. That would look ugly, but the static constructor would only run once for each type (byte
, int
, etc.) you used. The static constructor could throw an exception if people used a wrong T
. Here's what the static constructor may look like:
static Foo()
{
if (typeof(T) == typeof(byte))
readMethod = (Action<T[]>)(Delegate)(Action<byte[]>)ExternalAPI.Read;
else if (typeof(T) == typeof(int))
readMethod = (Action<T[]>)(Delegate)(Action<int[]>)ExternalAPI.Read;
else if (typeof(T) == typeof(float))
readMethod = (Action<T[]>)(Delegate)(Action<float[]>)ExternalAPI.Read;
else if (typeof(T) == typeof(double))
readMethod = (Action<T[]>)(Delegate)(Action<double[]>)ExternalAPI.Read;
else
throw new Exception("The type parameter T can't be " + typeof(T));
}
Edit: Inspired by the first comment below, here's an attempt to make the static constructor use reflection instead:
static Foo()
{
var methInfo = typeof(ExternalAPI).GetMethod("Read", new[] { typeof(T[]), });
if (methInfo == null)
throw new Exception("ExternalAPI has no suitable method for " + typeof(T[]));
readMethod = (Action<T[]>)Delegate.CreateDelegate(typeof(Action<T[]>), methInfo);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With