7

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?

Community
  • 1
  • 1
Bill Carey
  • 1,395
  • 1
  • 11
  • 20
  • 1
    This is extremely similar to the question [C# generics not recognizing type](http://stackoverflow.com/questions/14130379/). In that thread there were three possible solutions, all keeping the generic approach `Foo`. These three were: (1) Use a cast to `dynamic` just before you call the API, as in `ExternalAPI.Read((dynamic)buffer);`. (2) Use reflection to find the correct overload to call. (3) Use a delegate to get a type-safe way to call the method, as in `readDelegate(buffer);` where `readDelegate` can be a field of type `Action` in this case, to be initialized with a "method group". – Jeppe Stig Nielsen Jan 04 '13 at 20:18

3 Answers3

3

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[]))
        {
            ...
        }
    }
}
Jon B
  • 51,025
  • 31
  • 133
  • 161
  • I was writing the same solution with the is keyword, but I think this is better. – Nick Bray Jan 04 '13 at 18:57
  • +1. Since generics provide only compile-time method resolution one have to do some sort of run-time detection in such case. If is ok for small number of choices - larger number may need something like map of type-to-handler. – Alexei Levenkov Jan 04 '13 at 18:57
2

I know it's a sad and redundant solution, but I think that it can't be done in a better way than this:

public void Stuff()
{
    var bufferBytes = buffer as byte[];
    if (bufferBytes != null)
    {
        ExternalAPI.Read(bufferBytes);
        return;
    }
    var bufferInts = buffer as int[];
    if (bufferInts != null)
    {
        ExternalAPI.Read(bufferInts);
        return;
    }
    var bufferDoubles = buffer as double[];
    if (bufferDoubles != null)
    {
        ExternalAPI.Read(bufferDoubles);
        return;
    }
    ExternalAPI.Read(buffer);
}

To improve performance by casting as less as possible, reorder the checks and put the ones which are most frequently called on the top of the if/else chain. Even if the code could be made shorter by a if (buffer is byte[]) ExternalAPI.Read(buffer as byte[]); else... model, that would represent useless overhead (since you would basically be casting buffer twice).

e_ne
  • 8,340
  • 32
  • 43
  • Thanks - this will work nicely. I can encapsulate all the boilerplate and make sure no consumers of my code will ever see it. Luckily it's not in a performance critical part of the application, so I can stomach the reflection. – Bill Carey Jan 04 '13 at 19:08
  • 1
    @BillCarey That isn't reflection. – Servy Jan 04 '13 at 19:14
  • True enough. Better to say then, that I can stomach extraneous casting. – Bill Carey Jan 04 '13 at 19:16
  • 1
    Eve: _"To improve performance by using Reflection as less as possible"_ Like Servy says, this is not reflection. – Jeppe Stig Nielsen Jan 04 '13 at 20:22
  • @JeppeStigNielsen I'll edit my answer. I was under the (wrong) impression that the `is` operator used Reflection internally but, after having done some research, I realized that it doesn't. Thanks for pointing that out. – e_ne Jan 04 '13 at 20:29
2

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);
}
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 1
    The generic-static-type approach is very powerful, especially when combined with Reflection. It would be relatively easy to design the class so that the first time it was used with a particular type it would try to bind an appropriate overload in ExternalApi. If you did that, your class would be able to automatically support any future overloads that get added to future versions of ExternalApi without your class having to know anything about them. Note that one could attempt the binding in the static class constructor, but that's a lousy place to throw an exception. – supercat Jan 04 '13 at 22:02
  • @supercat Agree with most og your comment. Regarding _"lousy place to throw an exception"_: With a generic class like `class Foo`, some people are of the opinion that the best place to throw an exception if the `T` type parameter is somehow illegal, is in the static constructor. – Jeppe Stig Nielsen Jan 04 '13 at 22:40
  • Some people may think it's fine to throw in the static constructor--taking the view that "the sooner the better"--but I disagree with that view. Among other things, a static constructor can only throw an exception once. If multiple attempts are made to perform the same illegal operation, every such attempt should yield the same exception. If the static constructor sets a delegate to a method which will try to use Reflection to find the overload, the first use would perform the Reflection as appropriate. Another approach... – supercat Jan 04 '13 at 22:57
  • ...would be to have the static constructor try to bind the delegate within a `Try/Catch` block, and have the `Catch` block set the static delegate to point to a method that will throw a `MissingMethodException` or something similar. – supercat Jan 04 '13 at 22:58
  • @supercat Did you see I edited my answer earlier? Instead of throwing, I could have said `readMethod = buff => { throw new Something(...); };`, but I think I like throwing in the static constructor more. – Jeppe Stig Nielsen Jan 04 '13 at 23:24
  • I haven't tested your code, but when I tested such constructs myself I found that there are some cases where Reflection will throw exceptions which you should probably catch and encapsulate as an `InnerException` of something like `MissingMethodException`. If e.g. ExternalApi implements overloads for `IFoo` and `IBar`, but not for type `FooBar` which implements both interfaces, Reflection will throw an exception rather than returning null. – supercat Jan 05 '13 at 00:34
  • @supercat According to [the documentation](http://msdn.microsoft.com/en-us/library/6hy0h0z1.aspx) this method returns `null` if no method is found. It does mention situations where an exception is thrown, but it seems like "impossible" situations, and I don't want to write `try`-`catch` code for that. It's something I believe can never happen, so why would I `try`? Give me that exception in my head if I'm wrong. – Jeppe Stig Nielsen Jan 05 '13 at 10:18
  • I mentioned one situation where it can occur--and probably not the only one. If Reflection finds two or more overloads, and it cannot determine that one is definitely better than the other, it will throw an exception rather than returning null. The present version of `ExternalApi` doesn't contain two or more methods that could be ambiguous under any circumstances, but future versions could at least theoretically do so (and one purpose of using Reflection was to handle the possibility of future changes to that assembly). – supercat Jan 05 '13 at 16:57