Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to force an external class to call a specialized overload when the calling class is, itself, generic?

Tags:

c#

generics

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?

like image 369
Bill Carey Avatar asked Jan 04 '13 18:01

Bill Carey


2 Answers

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[]))
        {
            ...
        }
    }
}
like image 125
Jon B Avatar answered Dec 18 '22 16:12

Jon B


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);
}
like image 40
Jeppe Stig Nielsen Avatar answered Dec 18 '22 16:12

Jeppe Stig Nielsen