Nie można przekonwertować wartości typu „T” na

146

Jest to prawdopodobnie pytanie nowicjusza, ale Google, co zaskakujące, nie udzieliło odpowiedzi.

Mam tę raczej sztuczną metodę

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

    return t;
}

Pochodząc z tła C ++, spodziewałem się, że to zadziała. Jednak kompilacja nie powiodła się z opcją „Nie można niejawnie przekonwertować typu„ T ”na łańcuch” i „Nie można przekonwertować typu„ T ”na ciąg znaków” dla obu powyższych przypisań.

Albo robię coś złego koncepcyjnie, albo po prostu mam złą składnię. Proszę, pomóż mi to rozwiązać.

Dziękuję Ci!

Alex
źródło
20
IMO, jeśli sprawdzasz typy w swoim kodzie ogólnym, prawdopodobnie typy ogólne nie są właściwym rozwiązaniem Twojego problemu.
Austin Salonen
Wyrażenie typeof(T) == typeof(string)jest rozpoznawane w czasie wykonywania, a nie w czasie kompilacji. Dlatego następujący wiersz w bloku jest nieprawidłowy.
Steve Guidi
8
(T) Convert.ChangeType (newT1, typeof (T))
vsapiha
2
@vsapiha, działa tylko wtedy, gdy obiekt implementuje IConvertible. Słodycz, jeśli to robi.
ouflak

Odpowiedzi:

285

Mimo że jest wewnątrz z ifbloku, kompilator nie wie, że Tjest string.
Dlatego nie pozwala ci rzucać. (Z tego samego powodu, że nie może oddać DateTimedo string)

Musisz rzucać do object(do którego każdy Tmoże rzucać), a stamtąd do string(ponieważ objectmożna rzucać do string).
Na przykład:

T newT1 = (T)(object)"some text";
string newT2 = (string)(object)t;
SLaks
źródło
2
To działa! Domyślam się, że drugim podobnym powinno być również T newT2 = (T) (obiekt) t; chociaż to nie jest operacja.
Alex
2
Dodatek: szablony C ++ są zasadniczo wycinane i wklejane w czasie kompilacji z podstawionymi poprawnymi wartościami. W języku C # rzeczywisty szablon ogólny (a nie jego „wystąpienie”) istnieje po kompilacji, a zatem musi (przepraszam za kalambur) być generyczny w określonych granicach typu.
(string) (obiekt) t; jednak nic tutaj nie robi, równie dobrze mógłby to pominąć, czyli (string) (obiekt), czyli
Doggett
6
Dlaczego po prostu nie przesyłać używając „jako string”? Na przykład kompiluje się dobrze (dosłownie skompilowałem to bez błędów), gdy userDefinedValue jest typu T:var isBlank = (userDefinedValue is string) && String.IsNullOrWhiteSpace(userDefinedValue as string);
Triynko,
1
Wydaje się to pomyłką projektantów kompilatora. Jeśli wszystkie T mogą być jawnie rzutowane na obiekt, a wszystkie obiekty mogą być jawnie rzutowane na łańcuch, to powinna istnieć reguła przechodnia, zgodnie z którą T może być jawnie rzutowana na łańcuch. Jeśli wykonasz rzutowanie nieprawidłowo, powinien wystąpić błąd w czasie wykonywania.
P.Brian.Mackey
10

Obie linie mają ten sam problem

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

Kompilator nie wie, że T jest ciągiem znaków, więc nie ma możliwości wiedzieć, jak to przypisać. Ale skoro sprawdziłeś, możesz to po prostu wymusić

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

nie musisz rzucać t, ponieważ jest to już ciąg, musisz również dodać ograniczenie

where T : class
Doggett
źródło
2
Źle. To się nie skompiluje. Zobacz moją odpowiedź.
SLaks
2
Kompiluje się dobrze (z tym, gdzie to znaczy, dodał, że kilka sekund po wysłaniu, mógł to przeoczyć). Ups, zapomniałem zmienić obsadę
Doggett
2

Znam podobny kod, który OP zamieścił w tym pytaniu z generycznych parserów. Z punktu widzenia wydajności należy użyć Unsafe.As<TFrom, TResult>(ref TFrom source), które można znaleźć w pakiecie NuGet System.Runtime.CompilerServices.Unsafe . Pozwala to uniknąć pakowania typów wartości w tych scenariuszach. Myślę też, że Unsafe.Asskutkuje to mniejszą ilością kodu maszynowego produkowanego przez JIT niż dwukrotne rzutowanie (przy użyciu (TResult) (object) actualString), ale nie sprawdziłem tego.

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 zostaną zastąpione przez JIT z wydajnymi instrukcjami kodu maszynowego, jak widać w oficjalnym repozytorium CoreFX:

Kod źródłowy niebezpiecznego

feO2x
źródło
1

Jeśli szukasz jawnych typów, dlaczego deklarujesz te zmienne jako 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;
}
Austin Salonen
źródło
6
Druga linia tworzy zmienną typu T.
SLaks
Pytasz po co sprawdzać typ? Załóżmy, że masz podstawowy typ pola, który przechowuje objectwartości, z typami pochodnymi, które przechowują stringwartości. Załóżmy, że te pola mają również wartość „DefaultIfNotProvided”, więc musisz sprawdzić, czy wartość podana przez użytkownika (która może być obiektem, ciągiem lub nawet liczbowym prymitywem) jest równoważna default(T). Ciąg może być traktowany jako specjalny przypadek, w którym pusty / biały ciąg jest traktowany tak samo jak domyślny (T), więc możesz chcieć sprawdzić, czy T userValue; var isBlank = (userValue is string) && String.IsNullOrWhitespace(userValue as string);.
Triynko
0

Otrzymasz również ten błąd, jeśli masz ogólną deklarację zarówno dla swojej klasy, jak i metody. Na przykład poniższy kod podaje ten błąd kompilacji.

public class Foo <T> {

    T var;

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

}

Ten kod nie kompiluje się (uwaga T została usunięta z deklaracji metody):

public class Foo <T> {

    T var;

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

}
Jan
źródło
-5

Zmień tę linię:

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

Dla tej linii:

if (t.GetType() == typeof(string))
Serch
źródło
1
są takie same
bigworld 12
Oba są takie same ... po prostu używając słowa kluczowego języka, a nie używając interfejsów API biblioteki klas.
Abdulhameed