Как использовать дженерики, чтобы передать аргумент в неуниверсальный способ?

почему следующий код не компилируется? Как я могу создать общий метод, который вызывает соответствующий " BitConverter.Перегрузка GetBytes "основана на том, является ли общий тип "int", "bool", "char" и т. д.? В более общем плане, как я могу создать универсальный метод, который вызывает не-универсальный метод на основе типа универсального параметра?

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

7 ответов


в более общем плане, как я могу создать универсальный метод, который вызывает не универсальный метод на основе типа универсального параметра?

В общем, вы не можете, если только рассматриваемый метод не принимает System.Object в качестве параметра. Проблема в том, что generic не ограничен только типами, которые были бы разрешены аргументами вызова метода.

самое близкое, что вы можете сделать, это использовать привязку времени выполнения:

public byte[] GetBytes <TSource> (TSource input)
{
     dynamic obj = input;
     BitConverter.GetBytes(obj);
}

это подталкивает привязку метода логика для выполнения и будет бросать, если нет соответствующего метода для вызова.


причина этого не работает в том, что общие методы по-прежнему разрешают вызовы методов, сделанных в них статически. С TSource может быть любой тип вообще, он может вызывать только метод на BitConverter С


где код вызова BitConverter.GetBytes, типа TSource, поэтому вызов не может быть статически связан компилятором. Вы можете обойти это с помощью динамического вызова, то есть он будет компилироваться нормально, а затем разрешаться во время выполнения:

…
public byte[] GetBytes(dynamic input)
{
    return BitConverter.GetBytes(input);
}

вы заплатите штраф за использование динамического вызова, и если нет подходящего метода для вызова, вы получите исключение во время выполнения.


учитывая, что есть "только" 10 перегрузок BitConverter.GetBytes, Не невозможно отразить их все явно следующим образом:

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

это не общий (который вы просили), и не масштабируется до более сложных примеров, но если числа малы, то это an подход к рассмотрению.


Если вы готовы принять удар производительности, вы можете использовать отражение и расширение для объекта GetBytes. образец....

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

поэтому GetBytes принимает объект. Затем он получает его тип и пытается получить MethodInfo из BitConverter на основе типа передаваемого объекта. Если он не может найти перегрузку, которая принимает этот тип в качестве параметра, он создает исключение InvalidOperation. Если это так, он вызывает его передачу в экземпляре obj в качестве значения и возвращает массив байтов.

например. Код использования,

//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.

вы должны использовать отражение, чтобы сделать это.

  1. скачать GetBytes группа методов из BitConverter статический тип.
  2. вытащите перегрузку, для которой первый параметр имеет тип TSource.
  3. вызовите этот конкретный метод через Invoke метод.

если вы не знакомы с некоторыми из этого, я могу расширить ответ с кодом для этих шагов.

Edit: или просто используйте dynamic как и другие, предлагают и сохранить немного поработать.


ваш код не компилируется, потому что компилятор не может проверить, что любой тип для TSource принимается BitConverter.GetBytes(). Вы можете проверить для каждого типа индивидуально и бросить:

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