Значение типа 'T' не может быть преобразовано в

146

Вероятно, это вопрос новичка, но на удивление Google не дал ответа.

У меня есть этот довольно искусственный метод

T HowToCast<T>(T t)
{
    if (typeof(T) == typeof(string))
    {
        T newT1 = "some text";
        T newT2 = (string)t;
    }

    return t;
}

Исходя из опыта C ++, я ожидал, что это сработает. Тем не менее, он не может быть скомпилирован с «Не удается неявно преобразовать тип« T »в строку» и «Не удается преобразовать тип« T »в строку» для обоих вышеупомянутых назначений.

Я либо делаю что-то концептуально неправильно, либо просто у меня неправильный синтаксис. Пожалуйста, помогите мне разобраться с этим.

Спасибо!

Alex
источник
20
IMO, если вы проверяете типы в своем коде дженериков, то дженерики, вероятно, не являются правильным решением вашей проблемы.
Остин Салонен
Выражение typeof(T) == typeof(string)разрешается во время выполнения, а не во время компиляции. Таким образом, следующая строка в блоке недействительна.
Стив Гуиди
8
(T) Convert.ChangeType (newT1, typeof (T))
vsapiha
2
@vsapiha, работает только если объект реализует IConvertible. Сладость, если это так.
ouflak

Ответы:

285

Даже если он находится внутри ifблока, компилятор не знает, что это Tтакое string.
Следовательно, он не позволяет вам разыгрывать. (По той же причине , что вы не можете бросить DateTimeв string)

Вам нужно привести к object, (который любой Tможет привести к), а оттуда к string(так как objectможно привести к string).
Например:

T newT1 = (T)(object)"some text";
string newT2 = (string)(object)t;
SLaks
источник
2
Это работает! Я предполагаю, что второе подобное также должно быть T newT2 = (T) (object) t; хотя это не оп.
Alex
2
Дополнение: шаблоны C ++ по существу вырезаны и вставлены во время компиляции с заменой правильных значений. В C # фактический универсальный шаблон (а не его «экземпляр») существует после компиляции и, таким образом, должен (извините за каламбур) быть универсальным через границы указанного типа.
(строка) (объект) т; здесь ничего не делает, может с таким же успехом не учитывать это (строка) (объект), то есть
Доггетт
6
Почему бы просто не использовать «как строку»? Например, это прекрасно компилируется (я буквально просто скомпилировал его без ошибок), когда userDefinedValue имеет тип T:var isBlank = (userDefinedValue is string) && String.IsNullOrWhiteSpace(userDefinedValue as string);
Triynko
1
Это похоже на ошибку дизайнеров компилятора. Если все T могут быть точно приведены к объекту, и весь объект может быть точно приведен к строке, тогда должно существовать транзитивное правило, что T может быть явно приведен к строке. Если вы неправильно выполните приведение, то должна произойти ошибка runime.
P.Brian.Mackey
10

Обе линии имеют одинаковую проблему

T newT1 = "some text";
T newT2 = (string)t;

Компилятор не знает, что T является строкой, и поэтому не может знать, как его назначить. Но так как вы проверили, вы можете просто заставить его

T newT1 = "some text" as T;
T newT2 = t; 

вам не нужно приводить t, так как это уже строка, также нужно добавить ограничение

where T : class
Доджетт
источник
2
Неправильно. Это не скомпилируется. Смотри мой ответ.
SLaks
2
Компилируется просто отлично (с тем, где это, добавлено, что через несколько секунд после того, как я написал, возможно, пропустил это). Упс, нм забыл сменить актерский состав
Доггетт
2

Я знаю похожий код, который ОП выложил в этом вопросе из универсальных парсеров. С точки зрения производительности, вы должны использовать Unsafe.As<TFrom, TResult>(ref TFrom source), который можно найти в пакете System.Runtime.CompilerServices.Unsafe NuGet. Это позволяет избежать упаковки для типов значений в этих сценариях. Я также думаю, что это Unsafe.Asприводит к меньшему количеству машинного кода, создаваемого JIT, чем приведение дважды (с использованием (TResult) (object) actualString), но я этого не проверял.

public TResult ParseSomething<TResult>(ParseContext context)
{
    if (typeof(TResult) == typeof(string))
    {
        var token = context.ParseNextToken();
        string parsedString = token.ParseToDotnetString();
        return Unsafe.As<string, TResult>(ref parsedString);
    }
    else if (typeof(TResult) == typeof(int))
    {
        var token = context.ParseNextToken();
        int parsedInt32 = token.ParseToDotnetInt32();
        // This will not box which might be critical to performance
        return Unsafe.As<int, TResult>(ref parsedInt32); 
    }
    // other cases omitted for brevity's sake
}

Unsafe.As будет заменен JIT с эффективными инструкциями машинного кода, как вы можете увидеть в официальном репозитории CoreFX:

Исходный код Unsafe.As

feO2x
источник
1

Если вы проверяете явные типы, почему вы объявляете эти переменные как Ts?

T HowToCast<T>(T t)
{
    if (typeof(T) == typeof(string))
    {
        var newT1 = "some text";
        var newT2 = t;  //this builds but I'm not sure what it does under the hood.
        var newT3 = t.ToString();  //for sure the string you want.
    }

    return t;
}
Остин Салонен
источник
6
Вторая строка создает переменную типа T.
Слакс
Вы спрашиваете, зачем проверять тип? Предположим, у вас есть базовый тип поля, в котором хранятся objectзначения, с производными типами, в которых хранятся stringзначения. Предположим, что эти поля также имеют значение «DefaultIfNotProvided», поэтому вам необходимо проверить, эквивалентно ли предоставленное пользователем значение (которое может быть объектом, строкой или даже числовым примитивом) default(T). Строка может рассматриваться как особый случай, когда пустая строка / пробел обрабатывается так же, как по умолчанию (T), поэтому вы можете проверить, есть ли T userValue; var isBlank = (userValue is string) && String.IsNullOrWhitespace(userValue as string);.
Трийнко
0

Вы также получите эту ошибку, если у вас есть общее объявление для вашего класса и вашего метода. Например, приведенный ниже код выдает эту ошибку компиляции.

public class Foo <T> {

    T var;

    public <T> void doSomething(Class <T> cls) throws InstantiationException, IllegalAccessException {
        this.var = cls.newInstance();
    }

}

Этот код компилируется (примечание T удалено из объявления метода):

public class Foo <T> {

    T var;

    public void doSomething(Class <T> cls) throws InstantiationException, IllegalAccessException {
        this.var = cls.newInstance();
    }

}
Джон
источник
-5

Измените эту строку:

if (typeof(T) == typeof(string))

Для этой строки:

if (t.GetType() == typeof(string))
Серч
источник
1
они одинаковы
bigworld12
Оба одинаковы ... только с использованием ключевого слова языка и API библиотек классов.
Абдулхамед