Невозможно неявно преобразовать тип Int в T


90

Я могу позвонить Get<int>(Stat);илиGet<string>(Name);

Но при компиляции получаю:

Невозможно неявно преобразовать тип int в T

и то же самое для string.

public T Get<T>(Stats type) where T : IConvertible
{
    if (typeof(T) == typeof(int))
    {
        int t = Convert.ToInt16(PlayerStats[type]);
        return t;
    }
    if (typeof(T) == typeof(string))
    {
        string t = PlayerStats[type].ToString();
        return t;
    }
}

6
Вы, вероятно, думаете, что блок if проверил, что T является int, поэтому внутри блока вы знаете, что T является int, и вы должны иметь возможность неявно преобразовывать int в T. Но компилятор не предназначен для этого рассуждения, он просто знает что обычно T не является производным от int, поэтому не позволяет неявное преобразование. (И если бы компилятор поддерживал это, верификатор не стал бы, поэтому скомпилированная сборка была бы непроверяемой.)
JG Weissman

Ответы:


132

Каждый раз, когда вы обнаруживаете, что включаете тип в универсальном, вы почти наверняка делаете что-то не так . Дженерики должны быть общими ; они должны работать одинаково, полностью независимо от типа .

Если T может быть только int или string, тогда вообще не пишите свой код таким образом. Напишите два метода, один из которых возвращает int, а другой - строку.


1
Получите <Car> там, где автомобиль, орудие IConvertible, вызовет поломку. Когда кто-то увидит, что у вас есть общий метод, они подумают, что могут передать все, что реализует IConvertible.
Tjaart

10
Я могу лишь частично согласиться с вами, @Eric. У меня есть ситуация, когда мне нужно разбирать массивы, хранящиеся в XML-тегах. Проблема в том, что спецификация, которой следует XML-документ (COLLADA в моем случае), говорит, что такие массивы могут быть не только float, int и bool, но и некоторые настраиваемые типы. Однако, если вы получите float [] (теги-массивы содержат тип хранимых данных в своих именах: float_array хранит числа с плавающей запятой), вам необходимо проанализировать строку как массив floats, что требует использования некоторого IFormatProvider). Я, очевидно, не могу использовать "T.Parse (...)". Поэтому для небольшого подмножества случаев мне нужно использовать такое переключение.
rbaleksandar

1
Этот ответ убережет вас от кроличьей норы. Я хотел сделать функцию общей для int, int?, bool, bool?, string, и это казалось невозможным.
Джесс

Это делает переключение на универсальный перечислимый тип практичным.
Дэвид А. Грей

1
Я не хотел использовать это как ответ. Но он прав. Я хотел проверить тип и, если он конкретный, установить для него свойство. Решением было создать метод, принимающий строго типизированный параметр.
Мэтт Доуди

141

Вы должны иметь возможность просто использовать Convert.ChangeType()вместо своего собственного кода:

public T Get<T>(Stats type) where T : IConvertible
{
    return (T) Convert.ChangeType(PlayerStats[type], typeof(T));
}

21
Как насчетreturn (T)(object)PlayerStats[type];
maxp

11
public T Get<T>(Stats type ) where T : IConvertible
{
    if (typeof(T) == typeof(int))
    {
        int t = Convert.ToInt16(PlayerStats[type]);
        return (T)t;
    }
    if (typeof(T) == typeof(string))
    {
        string t = PlayerStats[type].ToString();
        return (T)t;
    }
}

2
return (T) t;потому что никаких нулевых проверок не требуется.
BoltClock

Это выше не будет компилироваться для меня. T должен быть ссылочным типом для "as" для компиляции.
Роберт Шмидт

9

ChangeTypeвероятно, ваш лучший вариант. Мое решение похоже на то, которое предоставляет BrokenGlass, с небольшой логикой попытки отлова.

static void Main(string[] args)
{
    object number = "1";
    bool hasConverted;
    var convertedValue = DoConvert<int>(number, out hasConverted);

    Console.WriteLine(hasConverted);
    Console.WriteLine(convertedValue);
}

public static TConvertType DoConvert<TConvertType>(object convertValue, out bool hasConverted)
{
    hasConverted = false;
    var converted = default(TConvertType);
    try
    {
        converted = (TConvertType) 
            Convert.ChangeType(convertValue, typeof(TConvertType));
        hasConverted = true;
    }
    catch (InvalidCastException)
    {
    }
    catch (ArgumentNullException)
    {
    }
    catch (FormatException)
    {
    }
    catch (OverflowException)
    {
    }

    return converted;
}

Мой вариант использования - это конкретный класс, производный от общего абстрактного класса. Класс помечен как абстрактный, потому что он определяет абстрактный метод, который работает с общим частным членом базового класса. Универсальный использует ограничение C # 7.3 Enum для своего универсального типа. Я только что успешно прошел тест, и он работает именно так, как я надеялся.
Дэвид А. Грей

8

Попробуй это:

public T Get<T>(Stats type ) where T : IConvertible
{
    if (typeof(T) == typeof(int))
    {
        return (T)(object)Convert.ToInt16(PlayerStats[type]);

    }
    if (typeof(T) == typeof(string))
    {

        return (T)(object)PlayerStats[type];
    }
}

Спасибо, это помогло, мне нужно другое. Я пишу фиктивный метод для существующего статического метода, чтобы я мог его протестировать. Используя это osherove.com/blog/2012/7/8/…
Esen

8

Фактически, вы можете просто преобразовать его в, objectа затем в T.

T var = (T)(object)42;

Пример для bool:

public class Program
{
    public static T Foo<T>()
    {
        if(typeof(T) == typeof(bool)) {
            return (T)(object)true;
        }

        return default(T);
    }

    public static void Main()
    {
        bool boolValue = Foo<bool>(); // == true
        string stringValue = Foo<string>(); // == null
    }
}

Иногда такое поведение желательно. Например, при реализации или переопределении универсального метода из базового класса или интерфейса вы хотите добавить некоторые другие функции в зависимости от Tтипа.


6

Учитывая, что логика @BrokenGlass ( Convert.ChangeType) не поддерживает тип GUID.

public T Get<T>(Stats type) where T : IConvertible
{
    return (T) Convert.ChangeType(PlayerStats[type], typeof(T));
}

Ошибка : недопустимое преобразование из System.String в System.Guid.

Вместо этого используйте приведенную ниже логику TypeDescriptor.GetConverter, добавив System.ComponentModelпространство имен.

public T Get<T>(Stats type) where T : IConvertible
{
    (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(PlayerStats[type])
}

Прочтите это .



0

Вы можете просто использовать, как показано ниже,

public T Get<T>(Stats type) where T : IConvertible
{
  if (typeof(T) == typeof(int))
  {
    int t = Convert.ToInt16(PlayerStats[type]);
    return t as T;
  }
 if (typeof(T) == typeof(string))
 {
    string t = PlayerStats[type].ToString();
    return t as T;
 }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.