类型 'T' 的值无法转换为

191
这可能是一个初学者的问题,但令人惊讶的是,谷歌并没有提供答案。
我有一个相对简单的方法。
T HowToCast<T>(T t)
{
    if (typeof(T) == typeof(string))
    {
        T newT1 = "some text";
        T newT2 = (string)t;
    }

    return t;
}

作为一个有C++背景的人,我本以为这会起作用。但是,上述两个赋值语句都无法编译通过,报出了“无法将类型‘T’隐式转换为字符串”和“无法将类型‘T’转换为字符串”的错误。

我可能在概念上做错了什么,或者只是语法有误。请帮助我解决这个问题。

谢谢!


24
如果你在泛型代码中检查类型,那么泛型可能不是解决你问题的正确方法。 - Austin Salonen
表达式 typeof(T) == typeof(string) 是在运行时而不是编译时解析的。因此,块中的以下行是无效的。 - Steve Guidi
9
将"newT1"转换为类型"T",使用Convert.ChangeType方法。 - vsapiha
2
@vsapiha,只有在对象实现IConvertible接口时才能工作。如果是这样的话,那就太棒了。 - ouflak
6个回答

346
尽管它在一个if块中,但编译器并不知道T是string。因此,它不允许你进行强制转换。(与无法将DateTime强制转换为string的原因相同)
您需要将其转换为object(任何T都可以转换),然后再将其转换为string(因为object可以转换为string)。
例如:
T newT1 = (T)(object)"some text";
string newT2 = (string)(object)t;

5
这可行!我猜测第二行也应该是T newT2 = (T)(object)t; 尽管这没有实际作用。 - Alex
2
附加:C++模板本质上是在编译时剪切和粘贴,正确的值被替换。在C#中,实际的通用模板(而不是“实例化”它)存在于编译后,因此必须(请原谅双关语)在指定类型边界上通用。 - user166390
8
为什么不直接使用 "as string" 进行类型转换呢?例如,当 userDefinedValue 的类型为 T 时,以下代码可以编译通过且无错误:var isBlank = (userDefinedValue is string) && String.IsNullOrWhiteSpace(userDefinedValue as string); - Triynko
4
这感觉像是编译器设计者的一个错误。如果所有的T都能被显式地转换成object,而且所有的object都能被显式地转换成string,那么应该存在一个传递规则,使得T能够被显式地转换成string。如果你错误地执行了这个转换,那么就会发生运行时错误。 - P.Brian.Mackey
2
如果这是一个错误,那么运行时应该像任何无效转换一样抛出InvalidCastException异常。但我不明白为什么它不能先转换为对象,然后再转换为T。我同意Triynko的观点,尽管我认为设计者们可能考虑了装箱/拆箱。 - Fernando Gómez
显示剩余4条评论

13

这两行代码都存在同样的问题

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
编译没问题(加了一个where子句,是我发帖几秒钟后才添加的,可能错过了)。哎呀,忘记改类型转换了。 - Doggett

8
我知道OP在这个问题中发布的代码与通用解析器非常相似。从性能的角度考虑,您应该使用Unsafe.As<TFrom, TResult>(ref TFrom source),它可以在System.Runtime.CompilerServices.Unsafe NuGet包中找到。在这些情况下,它避免了值类型的装箱。我还认为,Unsafe.As会产生比两次转换(使用(TResult) (object) actualString)更少的JIT生成的机器代码,但我没有检查过。
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 repo中看到:

Unsafe.As源代码


1

如果你正在检查明确的类型,为什么要将这些变量声明为 T 呢?

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 的变量。 - SLaks
你问为什么要检查类型?假设你有一个基本字段类型,它存储“object”值,并且有派生类型存储“string”值。假设这些字段还具有“DefaultIfNotProvided”值,因此您需要检查用户提供的值(可以是对象、字符串甚至是数字原语)是否等同于“default(T)” 。字符串可以被视为特殊情况,其中空/空格字符串与default(T)相同,因此您可能希望检查“T userValue; var isBlank = (userValue is string) && String.IsNullOrWhitespace(userValue as string);”。 - Triynko

-1
如果您的类和方法都有通用声明,也会出现此错误。例如,下面显示的代码将导致编译错误。
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();
    }

}

这是一个C#问题。不确定为什么您决定发布看起来像Java代码的内容。 - absoluteAquarian

-5

将此行更改为:

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

对于这行代码:

if (t.GetType() == typeof(string))

1
它们是相同的。 - Ahmed Fwela
两者是相同的...只是使用语言关键字与使用类库API的区别。 - Abdulhameed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接