Wert vom Typ 'T' kann nicht konvertiert werden


146

Dies ist wahrscheinlich eine Anfängerfrage, aber Google hat überraschenderweise keine Antwort geliefert.

Ich habe diese eher künstliche Methode

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

    return t;
}

Ich komme aus einem C ++ - Hintergrund und habe erwartet, dass dies funktioniert. Es kann jedoch nicht mit "Typ 'T' kann nicht implizit in Zeichenfolge konvertiert werden" und "Typ 'T' kann nicht in Zeichenfolge konvertiert werden" für beide oben genannten Zuweisungen kompiliert werden.

Ich mache entweder konzeptionell etwas falsch oder habe einfach die falsche Syntax. Bitte helfen Sie mir, das zu klären.

Danke dir!


20
IMO, wenn Sie Typen in Ihrem Generika-Code überprüfen, sind Generika wahrscheinlich nicht die richtige Lösung für Ihr Problem.
Austin Salonen

Der Ausdruck typeof(T) == typeof(string)wird zur Laufzeit und nicht zur Kompilierungszeit aufgelöst. Somit ist die folgende Zeile im Block ungültig.
Steve Guidi

8
(T) Convert.ChangeType (newT1, typeof (T))
vsapiha

2
@vsapiha, funktioniert nur, wenn das Objekt IConvertible implementiert. Süße, wenn doch.
Ouflak

Antworten:


285

Auch wenn es innerhalb eines ist ifBlock, wird der Compiler weiß nicht , dass Tist string.
Daher können Sie nicht wirken. (Aus dem gleichen Grund , dass Sie nicht werfen kann DateTimezu string)

Sie müssen zu object(zu dem jeder Twechseln kann) und von dort zu string(seit)object kann zu geworfen werden string).
Beispielsweise:

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

2
Das funktioniert! Ich vermute, das zweite wie sollte auch T newT2 = (T) (Objekt) t sein; obwohl das ein no op ist.
Alex

2
Ergänzung: C ++ - Vorlagen werden im Wesentlichen zur Kompilierungszeit ausgeschnitten und eingefügt, wobei die richtigen Werte ersetzt werden. In C # existiert die eigentliche generische Vorlage (keine "Instanziierung" davon) nach der Kompilierung und muss daher (entschuldigen Sie das Wortspiel) über die angegebenen Typgrenzen hinweg generisch sein.

(Zeichenfolge) (Objekt) t; tut hier aber nichts, könnte das genauso gut weglassen, das (string) (Objekt), das ist
Doggett

6
Warum nicht einfach mit "as string" besetzen? Zum Beispiel wird dies gut kompiliert (ich habe es buchstäblich nur fehlerfrei kompiliert), wenn userDefinedValue vom Typ ist T:var isBlank = (userDefinedValue is string) && String.IsNullOrWhiteSpace(userDefinedValue as string);
Triynko

1
Dies fühlt sich wie ein Fehler der Compiler-Designer an. Wenn alle T explizit in ein Objekt umgewandelt werden können und alle Objekte explizit in einen String umgewandelt werden können, sollte es eine transitive Regel geben, dass T explizit in einen String umgewandelt werden kann. Wenn Sie die Umwandlung falsch durchführen, sollte ein Laufzeitfehler auftreten.
P.Brian.Mackey

10

Beide Leitungen haben das gleiche Problem

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

Der Compiler weiß nicht, dass T eine Zeichenfolge ist, und kann daher nicht wissen, wie er diese zuweisen soll. Aber da Sie überprüft haben, können Sie es einfach mit erzwingen

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

Sie müssen das t nicht umwandeln, da es bereits eine Zeichenfolge ist. Außerdem müssen Sie die Einschränkung hinzufügen

where T : class

2
Falsch. Dies wird nicht kompiliert. Siehe meine Antwort.
SLaks

2
Kompiliert ganz gut (mit dem Wo ist das, fügte hinzu, dass ein paar Sekunden nachdem ich gepostet habe, das möglicherweise übersehen hat). Ups nm hat vergessen, die Besetzung zu ändern
Doggett

2

Ich kenne einen ähnlichen Code, den das OP in dieser Frage von generischen Parsern gepostet hat. Aus Sicht der Leistung sollten Sie diese verwenden Unsafe.As<TFrom, TResult>(ref TFrom source), die im System.Runtime.CompilerServices.Unsafe NuGet-Paket enthalten ist. In diesen Szenarien wird das Boxen für Werttypen vermieden. Ich denke auch, dass dies dazu Unsafe.Asführt , dass weniger Maschinencode von der JIT erzeugt wird als zweimal (mit (TResult) (object) actualString), aber ich habe das nicht überprüft.

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 wird durch die JIT durch effiziente Maschinencode-Anweisungen ersetzt, wie Sie im offiziellen CoreFX-Repo sehen können:

Quellcode von Unsafe.As


1

Wenn Sie nach expliziten Typen suchen, warum deklarieren Sie diese Variablen als 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;
}

6
In der zweiten Zeile wird eine Variable vom Typ erstellt T.
SLaks

Sie fragen, warum überprüfen Sie den Typ? Angenommen, Sie haben einen Basisfeldtyp, in dem objectWerte gespeichert stringwerden , und abgeleitete Typen, in denen Werte gespeichert werden. Angenommen, diese Felder haben auch den Wert "DefaultIfNotProvided". Sie müssen also überprüfen, ob der vom Benutzer angegebene Wert (der ein Objekt, eine Zeichenfolge oder sogar ein numerisches Grundelement sein kann) äquivalent ist default(T). Eine Zeichenfolge kann als Sonderfall behandelt werden, in dem eine leere Zeichenfolge / Leerzeichen genauso behandelt wird wie die Standardeinstellung (T). Daher möchten Sie möglicherweise überprüfen, ob T userValue; var isBlank = (userValue is string) && String.IsNullOrWhitespace(userValue as string);.
Triynko

0

Sie erhalten diesen Fehler auch, wenn Sie eine generische Deklaration für Ihre Klasse und Ihre Methode haben. Der unten gezeigte Code gibt beispielsweise diesen Kompilierungsfehler aus.

public class Foo <T> {

    T var;

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

}

Dieser Code wird kompiliert (Hinweis T aus der Methodendeklaration entfernt):

public class Foo <T> {

    T var;

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

}

-5

Ändern Sie diese Zeile:

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

Für diese Zeile:

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

1
sie sind die gleichen
bigworld12

Beide sind gleich ... nur mit dem Schlüsselwort language oder mit Klassenbibliotheks-APIs.
Abdulhameed
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.