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!

Alex
quelle
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;
SLaks
quelle
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
Doggett
quelle
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

feO2x
quelle
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;
}
Austin Salonen
quelle
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();
    }

}
John
quelle
-5

Ändern Sie diese Zeile:

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

Für diese Zeile:

if (t.GetType() == typeof(string))
Serch
quelle
1
sie sind die gleichen
bigworld12
Beide sind gleich ... nur mit dem Schlüsselwort language oder mit Klassenbibliotheks-APIs.
Abdulhameed