191

This is likely a a novice question, but google surprisingly did not provide an answer.

I have this rather artificial method

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

    return t;
}

Coming from a C++ background I have expected this to work. However, it fails to compile with "Cannot implicitly convert type 'T' to string" and "Cannot convert type 'T' to string" for both of the above assignments.

I am either doing something conceptually wrong or just have the wrong syntax. Please help me sort this one out.

Thank you!

CarenRose
  • 1,266
  • 1
  • 12
  • 24
Alex
  • 2,301
  • 2
  • 16
  • 15

6 Answers6

345

Even though it's inside of an if block, the compiler doesn't know that T is string.
Therefore, it doesn't let you cast. (For the same reason that you cannot cast DateTime to string)

You need to cast to object, (which any T can cast to), and from there to string (since object can be cast to string).
For example:

T newT1 = (T)(object)"some text";
string newT2 = (string)(object)t;
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 5
    This works! I am guessing the second like should also be T newT2 = (T)(object)t; although that's a no op. – Alex Nov 03 '10 at 22:59
  • 2
    Addition: C++ templates are essentially cut-and-paste at compile time with the correct values substituted. In C# the actual generic template (not an "instantiation" of it) exists after compilation and thus must (pardon the pun) be generic across the specified type bounds. –  Nov 03 '10 at 23:03
  • (string)(object)t; does nothing here though, might as well leave that out, the (string)(object) that is – Doggett Nov 03 '10 at 23:11
  • 8
    Why not just cast using "as string"? For example, this compiles fine (I literally just compiled it without errors) when userDefinedValue is of type `T`: `var isBlank = (userDefinedValue is string) && String.IsNullOrWhiteSpace(userDefinedValue as string);` – Triynko Dec 16 '15 at 05:48
  • 4
    This feels like a mistake by the compiler designers. If all T can be explcitly cast to object and all object can be explcitly cast to string then there should exist a transitive rule that T can be explicitly cast to string. If you incorrectly perform the cast then a runime error should occur. – P.Brian.Mackey May 02 '18 at 23:54
  • @P.Brian.Mackey: No; because that's usually a mistake. Just like `(string) new DateTime()`. – SLaks May 03 '18 at 02:07
  • 2
    @SLaks if it is a mistake, then the runtime should throw an InvalidCastException, just like with any invalid cast. But I don't see why it couldn't convert to object and then to T. I agree with Triynko, although I suppose the designers had boxing/unboxing in mind. – Fernando Gómez May 30 '19 at 23:32
13

Both lines have the same problem

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

The compiler doesn't know that T is a string and so has no way of knowing how to assign that. But since you checked you can just force it with

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

you don't need to cast the t since it's already a string, also need to add the constraint

where T : class
Doggett
  • 3,364
  • 21
  • 17
  • 2
    Wrong. This will not compile. See my answer. – SLaks Nov 03 '10 at 23:04
  • 2
    Compiles just fine (with the where that is, added that a few seconds after I posted, might have missed that). Oops nm forgot to change the cast – Doggett Nov 03 '10 at 23:07
8

I know similar code that the OP posted in this question from generic parsers. From a performance perspective, you should use Unsafe.As<TFrom, TResult>(ref TFrom source), which can be found in the System.Runtime.CompilerServices.Unsafe NuGet package. It avoids boxing for value types in these scenarios. I also think that Unsafe.As results in less machine code produced by the JIT than casting twice (using (TResult) (object) actualString), but I haven't checked that out.

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 will be replaced by the JIT with efficient machine code instructions, as you can see in the official CoreFX repo:

Source Code of Unsafe.As

feO2x
  • 5,358
  • 2
  • 37
  • 46
1

If you're checking for explicit types, why are you declaring those variables as T's?

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;
}
Austin Salonen
  • 49,173
  • 15
  • 109
  • 139
  • 6
    The second line creates a variable of type `T`. – SLaks Nov 03 '10 at 23:28
  • You ask why check the type? Suppose you have a base field type which stores `object` values, with derived types that store `string` values. Suppose these fields also have a "DefaultIfNotProvided" value, so you need to check whether the user-provided value (which could be an object or a string or even a numeric primitive) is equivalent to `default(T)`. String may be treated as a special case where an empty/whitespace string is treated the same as default(T), so you may want to check whether `T userValue; var isBlank = (userValue is string) && String.IsNullOrWhitespace(userValue as string);`. – Triynko Dec 16 '15 at 06:00
-1

You will also get this error if you have a generic declaration for both your class and your method. For example the code shown below gives this compile error.

public class Foo <T> {

    T var;

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

}

This code does compile (note T removed from method declaration):

public class Foo <T> {

    T var;

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

}
John
  • 3,458
  • 4
  • 33
  • 54
-5

Change this line:

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

For this line:

if (t.GetType() == typeof(string))
Josh Darnell
  • 11,304
  • 9
  • 38
  • 66
Serch
  • 7
  • 1