Rzucanie zmiennej za pomocą zmiennej Type


281

Czy w C # mogę rzutować zmienną typu object na zmienną typu T, gdzie T jest zdefiniowane w zmiennej Type?


12
Nie jest to ściśle tematyczne, ale wydajesz się dość niejasny, co oznacza „obsada”, dlatego dobrym pomysłem może być dokładne zrozumienie celu i semantyki operatora rzutowania. Oto dobry początek: blogs.msdn.com/ericlippert/archive/2009/03/19/…
Eric Lippert

2
Myślałem, że coś wymyśliłem. Jeśli masz Typezmienną, możesz użyć refleksji, aby utworzyć instancję tego typu. A następnie możesz użyć ogólnej metody, aby zwrócić pożądany typ, wywnioskując go z parametru tego typu. Niestety, każda metoda refleksji, która tworzy instancję typu, będzie miała typ zwracany object, więc Twoja ogólna CastByExamplemetoda również będzie używać object. Więc tak naprawdę nie ma na to sposobu, a nawet gdyby tak było, co byś zrobił z nowo rzuconym obiektem? Nie możesz użyć jego metod ani niczego, ponieważ nie znasz jego typu.
Kyle Delaney

@KyleDelaney Dziękuję, całkowicie się zgadzam! Jak próbowałem wyjaśnić w mojej odpowiedzi, tak naprawdę nie jest użyteczne rzucanie czegoś na inną rzecz, w którymś momencie nie zdefiniowałem typu, którego faktycznie używasz. Cały punkt typów to sprawdzanie typu czasu kompilatora. Jeśli potrzebujesz tylko wywołać obiekt, możesz użyć objectlub dynamic. Jeśli chcesz dynamicznie ładować moduły zewnętrzne, możesz mieć klasy współużytkowane wspólny interfejs i rzutować na niego obiekt. Jeśli nie kontrolujesz kodu strony trzeciej, utwórz małe opakowania i zaimplementuj na nim interfejs.
Zyphrax

Odpowiedzi:


203

Oto przykład obsady i konwersji:

using System;

public T CastObject<T>(object input) {   
    return (T) input;   
}

public T ConvertObject<T>(object input) {
    return (T) Convert.ChangeType(input, typeof(T));
}

Edytować:

Niektóre osoby w komentarzach twierdzą, że ta odpowiedź nie odpowiada na pytanie. Ale linia (T) Convert.ChangeType(input, typeof(T))stanowi rozwiązanie. Convert.ChangeTypeMetoda usiłuje przekształcić dowolny obiekt do rodzaju świadczonych jako drugi argument.

Na przykład:

Type intType = typeof(Int32);
object value1 = 1000.1;

// Variable value2 is now an int with a value of 1000, the compiler 
// knows the exact type, it is safe to use and you will have autocomplete
int value2 = Convert.ChangeType(value1, intType);

// Variable value3 is now an int with a value of 1000, the compiler
// doesn't know the exact type so it will allow you to call any
// property or method on it, but will crash if it doesn't exist
dynamic value3 = Convert.ChangeType(value1, intType);

Pisałem odpowiedź z rodzajowych, ponieważ myślę, że jest to bardzo prawdopodobne, Znak zapachy kodu kiedy chcesz obsadą a somethingaby a something elsebez obsługi rzeczywisty typ. Z odpowiednimi interfejsami, które nie powinny być potrzebne 99,9% razy. Być może istnieje kilka skrajnych przypadków, jeśli chodzi o refleksję, że może to mieć sens, ale zalecałbym unikanie tych przypadków.

Edycja 2:

Kilka dodatkowych wskazówek:

  • Staraj się, aby Twój kod był jak najbardziej bezpieczny dla typu. Jeśli kompilator nie zna typu, nie może sprawdzić, czy kod jest poprawny, a rzeczy takie jak autouzupełnianie nie będą działać. Mówiąc wprost : jeśli nie możesz przewidzieć typu (-ów) w czasie kompilacji, to w jaki sposób kompilator mógłby to zrobić ?
  • Jeśli klasy, z którymi pracujesz, implementują wspólny interfejs , możesz rzutować wartość na ten interfejs. W przeciwnym razie rozważ utworzenie własnego interfejsu i poproś klasy, aby zaimplementowały ten interfejs.
  • Jeśli pracujesz z bibliotekami zewnętrznymi, które dynamicznie importujesz, sprawdź także wspólny interfejs. W przeciwnym razie rozważ utworzenie małych klas opakowań, które implementują interfejs.
  • Jeśli chcesz wykonywać wywołania na obiekcie, ale nie dbasz o typ, zapisz wartość w zmiennej objectlub dynamic.
  • Generics może być świetnym sposobem na tworzenie kodu wielokrotnego użytku, który ma zastosowanie do wielu różnych typów, bez konieczności znajomości konkretnych typów.
  • Jeśli utkniesz, rozważ inne podejście lub refaktor kodu. Czy Twój kod naprawdę musi być tak dynamiczny? Czy musi uwzględniać każdy rodzaj?

145
Nie wiem, jak to pomaga OP. Ma zmienną typu, a nie Ttaką.
nawfal

12
@nawfal, w zasadzie linia Convert.ChangeType(input, typeof(T));daje rozwiązanie. Możesz łatwo zastąpić typeof(T)istniejącą zmienną typu. Lepszym rozwiązaniem (jeśli to możliwe) byłoby całkowite zapobieganie typowi dynamicznemu.
Zyphrax,

59
@Zyphrax, nie, nadal wymaga obsady, do Tktórej nie jest dostępna.
nawfal

4
Wiem, że wynikowy obiekt jest naprawdę typu, Tale nadal otrzymujesz tylko objectjako odniesienie. hmm, uznałem pytanie za interesujące w założeniu, że OP ma tylko Typezmienną i żadnych innych informacji. Jakby sygnaturą metody jest Convert(object source, Type destination):) Niemniej jednak dostaję punkt
nawfal

10
Jak to jest rozwiązanie tego pytania? Mam ten sam problem i nie mam ogólnego <T>. Mam tylko zmienną typu.
Nuri Tasdemir

114

Inne odpowiedzi nie wspominają o typie „dynamicznym”. Aby dodać jeszcze jedną odpowiedź, możesz użyć typu „dynamicznego” do przechowywania wynikowego obiektu bez konieczności rzutowania skonwertowanego obiektu na typ statyczny.

dynamic changedObj = Convert.ChangeType(obj, typeVar);
changedObj.Method();

Należy pamiętać, że przy użyciu opcji „dynamicznej” kompilator omija sprawdzanie typu statycznego, co może powodować ewentualne błędy w czasie wykonywania, jeśli użytkownik nie jest ostrożny.


19
To jest poprawna odpowiedź. Bez dynamicznego słowa kluczowego typeof (zmienionyObj) to „obiekt”. Z dynamicznym słowem kluczowym działa bezbłędnie, a typeof (zmieniony obiekt) poprawnie odzwierciedla ten sam typ co typeVar. Dodatkowo nie musisz (T) rzucać, czego nie możesz zrobić, jeśli nie znasz typu.
rushinge

5
Mam wyjątek „Obiekt musi implementować IConvertible” podczas korzystania z tego rozwiązania. Jakaś pomoc?
Nuri Tasdemir

@NuriTasdemir Trudno powiedzieć, ale uważam, że konwersja, którą wykonujesz, nie jest możliwa bez IConvertible. Jakie typy są zaangażowane w konwersję?
maulik13

Podczas gdy to działa, istnieje ograniczenie wydajności przy użyciu dynamiki. Odradzam ich używanie, chyba że pracujesz z innymi środowiskami wykonawczymi (do czego została zaprojektowana dynamika).
Bolo

19

Oto moja metoda rzutowania obiektu, ale nie na zmienną typu ogólnego, a na System.Type dynamicznie:

Tworzę wyrażenie lambda w czasie wykonywania przy użyciu System.Linq.Expressionstypu Func<object, object>, który rozpakowuje dane wejściowe, wykonuje pożądaną konwersję typu, a następnie podaje wynik w ramce. Nowy jest potrzebny nie tylko dla wszystkich typów, na które rzutuje się, ale także dla typów, które są rzutowane (z powodu etapu rozpakowywania). Tworzenie tych wyrażeń jest bardzo czasochłonne, ze względu na odbicie, kompilację i dynamiczne budowanie metod wykonywane pod maską. Na szczęście po utworzeniu wyrażenia można wywoływać wielokrotnie i bez nadmiernego obciążenia, więc każdą z nich przechowuję w pamięci podręcznej.

private static Func<object, object> MakeCastDelegate(Type from, Type to)
{
    var p = Expression.Parameter(typeof(object)); //do not inline
    return Expression.Lambda<Func<object, object>>(
        Expression.Convert(Expression.ConvertChecked(Expression.Convert(p, from), to), typeof(object)),
        p).Compile();
}

private static readonly Dictionary<Tuple<Type, Type>, Func<object, object>> CastCache
= new Dictionary<Tuple<Type, Type>, Func<object, object>>();

public static Func<object, object> GetCastDelegate(Type from, Type to)
{
    lock (CastCache)
    {
        var key = new Tuple<Type, Type>(from, to);
        Func<object, object> cast_delegate;
        if (!CastCache.TryGetValue(key, out cast_delegate))
        {
            cast_delegate = MakeCastDelegate(from, to);
            CastCache.Add(key, cast_delegate);
        }
        return cast_delegate;
    }
}

public static object Cast(Type t, object o)
{
    return GetCastDelegate(o.GetType(), t).Invoke(o);
}

Pamiętaj, że to nie jest magia. Przesyłanie nie występuje w kodzie, podobnie jak w przypadku dynamicsłowa kluczowego, konwertowane są tylko podstawowe dane obiektu. W czasie kompilacji wciąż musimy starannie zastanawiać się, jakiego typu może być nasz obiekt, co czyni to rozwiązanie niepraktycznym. Napisałem to jako włamanie do wywoływania operatorów konwersji zdefiniowanych przez dowolne typy, ale być może ktoś może znaleźć lepszy przypadek użycia.


2
Wymagausing System.Linq.Expressions;
Aaron D

4
Dla mnie ma to ten sam problem, co odpowiedź Zyphraxa. Nie mogę wywoływać metod na zwróconym obiekcie, ponieważ nadal jest on typu „obiekt”. Bez względu na to, czy użyję jego metody („a” poniżej), czy twojej metody („b” poniżej), otrzymuję ten sam błąd przy rzutowaniu (t) - „” t ”jest zmienną, ale jest używana jak typ.Type t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
muusbolla

Oryginalna odpowiedź @muusbolla Zyphrax używa zmiennych ogólnych i typów, a nie Type. Nie możesz rzutować przy użyciu normalnej składni rzutowania, jeśli wszystko, co masz, to obiekt Type. Jeśli chcesz mieć możliwość używania obiektu jako jakiegoś typu T w czasie kompilacji, a nie środowiska wykonawczego, musisz rzutować go przy użyciu zmiennej typu lub po prostu rzeczywistej nazwy typu. Możesz zrobić to pierwsze, używając odpowiedzi Zaphraxa.
Ashley,

8

Odkładając boksowanie i rozpakowywanie na bok dla uproszczenia, nie ma konkretnej akcji środowiska wykonawczego związanej z rzutowaniem wzdłuż hierarchii dziedziczenia. Jest to głównie czas kompilacji. Zasadniczo rzutowanie mówi kompilatorowi, aby traktował wartość zmiennej jako inny typ.

Co możesz zrobić po obsadzie? Nie znasz typu, więc nie będziesz mógł wywoływać żadnych metod. Nie byłoby nic specjalnego do zrobienia. W szczególności może być przydatny tylko wtedy, gdy znasz możliwe typy w czasie kompilacji, rzutuj go ręcznie i obsługuj każdą sprawę osobno za pomocą ifinstrukcji:

if (type == typeof(int)) {
    int x = (int)obj;
    DoSomethingWithInt(x);
} else if (type == typeof(string)) {
    string s = (string)obj;
    DoSomethingWithString(s);
} // ...

1
Czy możesz wyjaśnić to jaśniej w odniesieniu do mojego pytania?
theringostarrs

Próbuję wyjaśnić, co będziesz w stanie zrobić po tym? Nie można wiele zrobić, ponieważ kompilator C # wymaga pisania statycznego, aby móc zrobić użyteczną rzecz z obiektem
Mehrdad Afshari

Masz rację. Znam oczekiwane typy dwóch zmiennych, które są wysyłane do metody jako typ „obiekt”. Chcę rzutować na oczekiwane typy przechowywane w zmiennych i dodać je do kolekcji. Znacznie łatwiej jest rozgałęziać się na typie i próbować normalnych błędów rzutowania i wyłapywania.
theringostarrs

4
Twoja odpowiedź jest dobra, ale żeby być wybrednym, zauważam, że rzutowania nigdy nie wpływają na zmienne . Rzucanie zmiennej na zmienną innego typu nigdy nie jest legalne ; typy zmiennych są niezmienne w C #. Możesz rzutować tylko wartość zapisaną w zmiennej na inny typ.
Eric Lippert,

Czy wprowadzenie dynamicznego pisania w C # 4.0 w ogóle zmienia tę odpowiedź?
Daniel T.

6

Jak mogłeś to zrobić? Potrzebujesz zmiennej lub pola typu T, w którym możesz przechowywać obiekt po rzutowaniu, ale jak możesz mieć taką zmienną lub pole, jeśli znasz T tylko w czasie wykonywania? Więc nie, nie jest to możliwe.

Type type = GetSomeType();
Object @object = GetSomeObject();

??? xyz = @object.CastTo(type); // How would you declare the variable?

xyz.??? // What methods, properties, or fields are valid here?

3
Jeśli używasz klasy ogólnej, która definiuje metodę ze zwracaną wartością typu T, możesz to zrobić. Np. Parsowanie łańcucha do wystąpienia T i zwracanie go.
Oliver Friedrich,

7
Na szczęście nie jest to poprawna odpowiedź. Zobacz odpowiedź maulik13.
rushinge

3
Gdzie w imię Nieba znajdujesz CastTometodę Object?
ProfK

3

Jeśli chodzi o casting do typu Enum:

private static Enum GetEnum(Type type, int value)
    {
        if (type.IsEnum)
            if (Enum.IsDefined(type, value))
            {
                return (Enum)Enum.ToObject(type, value);
            }

        return null;
    }

I nazwiesz to tak:

var enumValue = GetEnum(typeof(YourEnum), foo);

Było to dla mnie niezbędne w przypadku uzyskania wartości atrybutu Opis kilku typów wyliczenia według wartości int:

public enum YourEnum
{
    [Description("Desc1")]
    Val1,
    [Description("Desc2")]
    Val2,
    Val3,
}

public static string GetDescriptionFromEnum(Enum value, bool inherit)
    {
        Type type = value.GetType();

        System.Reflection.MemberInfo[] memInfo = type.GetMember(value.ToString());

        if (memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), inherit);
            if (attrs.Length > 0)
                return ((DescriptionAttribute)attrs[0]).Description;
        }

        return value.ToString();
    }

i wtedy:

string description = GetDescriptionFromEnum(GetEnum(typeof(YourEnum), foo));
string description2 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum2), foo2));
string description3 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum3), foo3));

Alternatywnie (lepsze podejście) taki rzut mógłby wyglądać następująco:

 private static T GetEnum<T>(int v) where T : struct, IConvertible
    {
        if (typeof(T).IsEnum)
            if (Enum.IsDefined(typeof(T), v))
            {
                return (T)Enum.ToObject(typeof(T), v);
            }

        throw new ArgumentException(string.Format("{0} is not a valid value of {1}", v, typeof(T).Name));
    }

1

Po tym, jak nie znalazłem nic do obejścia, „Obiekt musi zaimplementować IConvertible” podczas korzystania z odpowiedzi Zyphrax (z wyjątkiem implementacji interfejsu). Próbowałem czegoś niekonwencjonalnego i pracowałem w mojej sytuacji.

Korzystanie z pakietu nuget Newtonsoft.Json ...

var castedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(myObject), myType);

1

Szkoda, problem polega na tym, że nie masz T.

masz tylko zmienną Type.

Wskazówka dla stwardnienia rozsianego, jeśli możesz zrobić coś takiego

TryCast<typeof(MyClass)>

gdyby rozwiązał wszystkie nasze problemy.


0

Nigdy nie zrozumiem, dlaczego potrzebujesz 50 reputacji, aby dodać komentarz, ale musiałem powiedzieć, że odpowiedź @Curt jest dokładnie tym, czego szukałem i mam nadzieję, że ktoś inny.

W moim przykładzie mam ActionFilterAttribute, którego użyłem do aktualizacji wartości dokumentu poprawki json. Nie wiedziałem, jaki jest model T dla dokumentu poprawki, musiałem serializować i deserializować go do zwykłego JsonPatchDocument, modyfikować go, a następnie, ponieważ miałem typ, serializować i deserializować go z powrotem do typu.

Type originalType = //someType that gets passed in to my constructor.

var objectAsString = JsonConvert.SerializeObject(myObjectWithAGenericType);
var plainPatchDocument = JsonConvert.DeserializeObject<JsonPatchDocument>(objectAsString);

var plainPatchDocumentAsString= JsonConvert.SerializeObject(plainPatchDocument);
var modifiedObjectWithGenericType = JsonConvert.DeserializeObject(plainPatchDocumentAsString, originalType );

-1
public bool TryCast<T>(ref T t, object o)
{
    if (
        o == null
        || !typeof(T).IsAssignableFrom(o.GetType())
        )
        return false;
    t = (T)o;
    return true;
}

2
Czy mógłbyś wskazać, czym ta odpowiedź różni się od innych odpowiedzi i gdzie to rozwiązanie jest odpowiednie?
Klaus Gütter

-2

jeszcze czystszy:

    public static bool TryCast<T>(ref T t, object o)
    {
        if (!(o is T))
        {
            return false;
        }

        t = (T)o;
        return true;
    }

-2

Jeśli chcesz rzutować obiekty w czasie wykonywania bez znajomości typu miejsca docelowego, możesz użyć odbicia, aby utworzyć dynamiczny konwerter.

To jest wersja uproszczona (bez metody generowania buforowania):

    public static class Tool
    {
            public static object CastTo<T>(object value) where T : class
            {
                return value as T;
            }

            private static readonly MethodInfo CastToInfo = typeof (Tool).GetMethod("CastTo");

            public static object DynamicCast(object source, Type targetType)
            {
                return CastToInfo.MakeGenericMethod(new[] { targetType }).Invoke(null, new[] { source });
            }
    }

możesz to nazwać:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.