7

我正在编写与无法更改的外部 API 接口的代码:

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);
}

重要的是我在读入数据时调用了正确的重载方法buffer,但是一旦数据被读入,我将对其进行通用处理。我第一次通过代码是:

public class Foo<T>
{
    T[] buffer;

    public void Stuff()
    {
        ExternalAPI.Foo(buffer);
    }
}

但是,C# 不会从 转换T[]byte[]. 有没有办法枚举T可以显式表示的类型?我试过使用where T :子句,但似乎没有办法说where T : {byte, int, float, double and nothing else ever}

遵循此处的建议:Generic constraint to match numeric types,我向泛型添加了约束,并且还在我的模拟 API 中添加了一个泛型方法,该方法以 aobject作为其参数

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);
    }
}

这将愉快地编译和运行,但唯一被调用的方法是Foo(object buffer),即使Tbyte。当调用类是泛型时,有没有办法强制非泛型类的方法使用最具体的重载?

4

3 回答 3

3

我以前也遇到过这种情况,我想出的唯一解决方案是测试类型T并调用适当的函数;

public class Foo<T>
{
    T[] buffer;

    public void Stuff()
    {
        var type = typeof(T);

        if (type == typeof(int[]))
        {
            ...
        }
        else if (type == typeof(double[]))
        {
            ...
        }
    }
}
于 2013-01-04T18:53:48.270 回答
2

我知道这是一个可悲且多余的解决方案,但我认为没有比这更好的方法了:

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);
}

为了通过尽可能少的转换来提高性能,请重新排序检查并将最常调用的检查放在if/else链的顶部。即使if (buffer is byte[]) ExternalAPI.Read(buffer as byte[]); else...模型可以使代码更短,那也将代表无用的开销(因为您基本上会进行buffer两次转换)。

于 2013-01-04T18:54:12.280 回答
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);
}
于 2013-01-04T21:07:19.107 回答