Casting einer Variablen mit einer Type-Variablen

280

Kann ich in C # eine Variable vom Typ Objekt in eine Variable vom Typ T umwandeln, wobei T in einer Typvariablen definiert ist?

Theringostarrs
quelle
12
Nicht ausschließlich themenbezogen, aber Sie scheinen sich nicht sicher zu sein, was "Besetzung" bedeutet. Daher ist es möglicherweise eine gute Idee, den Zweck und die Semantik des Besetzungsoperators genau zu verstehen. Hier ist ein guter Anfang: blogs.msdn.com/ericlippert/archive/2009/03/19/…
Eric Lippert
2
Ich dachte, ich hätte mir etwas ausgedacht. Wenn Sie eine TypeVariable haben, können Sie mithilfe der Reflektion eine Instanz dieses Typs erstellen. Anschließend können Sie eine generische Methode verwenden, um den gewünschten Typ zurückzugeben, indem Sie ihn aus einem Parameter dieses Typs ableiten. Leider hat jede Reflektionsmethode, die eine Instanz eines Typs erstellt, einen Rückgabetyp von object, sodass auch Ihre generische CastByExampleMethode verwendet wird object. Es gibt also wirklich keine Möglichkeit, dies zu tun, und selbst wenn dies der Fall wäre, was würden Sie mit dem neu gegossenen Objekt tun? Sie konnten seine Methoden oder irgendetwas nicht verwenden, weil Sie seinen Typ nicht kennen.
Kyle Delaney
@ KyleDelaney Danke, ich stimme vollkommen zu! Wie ich in meiner Antwort zu erklären versuchte, ist es nicht wirklich nützlich, etwas in eine andere Sache umzuwandeln, ohne irgendwann den Typ zu definieren, den Sie tatsächlich verwenden. Der springende Punkt bei Typen ist die Überprüfung des Compiler-Zeittyps. Wenn Sie nur das Objekt aufrufen müssen, können Sie objectoder verwenden dynamic. Wenn Sie externe Module dynamisch laden möchten, können Sie die Klassen eine gemeinsame Schnittstelle verwenden lassen und das Objekt in diese umwandeln. Wenn Sie den Code von Drittanbietern nicht steuern, erstellen Sie kleine Wrapper und implementieren Sie die Schnittstelle darauf.
Zyphrax

Antworten:

203

Hier ist ein Beispiel für eine Besetzung und einen Konvertiten:

using System;

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

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

Bearbeiten:

Einige Leute in den Kommentaren sagen, dass diese Antwort die Frage nicht beantwortet. Aber die Linie (T) Convert.ChangeType(input, typeof(T))bietet die Lösung. Die Convert.ChangeTypeMethode versucht, ein beliebiges Objekt in den als zweites Argument angegebenen Typ zu konvertieren.

Beispielsweise:

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);

Ich habe die Antwort mit Generika geschrieben, weil ich denke , es ist ein sehr wahrscheinlich zu unterzeichnen ist der Codegeruch , wenn Sie Guss wollen a somethingzu , a something elseohne dass eine tatsächliche Art der Handhabung. Mit geeigneten Schnittstellen sollte dies in 99,9% der Fälle nicht erforderlich sein. Es gibt vielleicht ein paar Randfälle, wenn es um Überlegungen geht, die sinnvoll sein könnten, aber ich würde empfehlen, diese Fälle zu vermeiden.

Bearbeiten 2:

Einige zusätzliche Tipps:

  • Versuchen Sie, Ihren Code so typsicher wie möglich zu halten. Wenn der Compiler den Typ nicht kennt, kann er nicht überprüfen, ob Ihr Code korrekt ist und Dinge wie die automatische Vervollständigung nicht funktionieren. Einfach gesagt: Wenn Sie die Typen zum Zeitpunkt der Kompilierung nicht vorhersagen können, wie könnte der Compiler dies dann tun ?
  • Wenn die Klassen, mit denen Sie arbeiten , eine gemeinsame Schnittstelle implementieren , können Sie den Wert in diese Schnittstelle umwandeln. Andernfalls sollten Sie eine eigene Schnittstelle erstellen und die Klassen diese Schnittstelle implementieren lassen.
  • Wenn Sie mit externen Bibliotheken arbeiten, die Sie dynamisch importieren, suchen Sie auch nach einer gemeinsamen Schnittstelle. Andernfalls sollten Sie kleine Wrapper-Klassen erstellen, die die Schnittstelle implementieren.
  • Wenn Sie das Objekt aufrufen möchten, sich aber nicht für den Typ interessieren, speichern Sie den Wert in einer objectoder dynamicVariablen.
  • Generika können eine großartige Möglichkeit sein, wiederverwendbaren Code zu erstellen, der für viele verschiedene Typen gilt, ohne die genauen Typen kennen zu müssen.
  • Wenn Sie nicht weiterkommen, ziehen Sie einen anderen Ansatz oder Code-Refactor in Betracht. Muss Ihr Code wirklich so dynamisch sein? Muss es irgendeinen Typ berücksichtigen, den es gibt?
Zyphrax
quelle
145
Ich weiß nicht, wie das OP hilft. Sie hat eine Typvariable, nicht Tals solche.
Nawfal
12
@nawfal, im Grunde Convert.ChangeType(input, typeof(T));gibt die Zeile die Lösung. Sie können leicht durch typeof(T)eine vorhandene Typvariable ersetzen . Eine bessere Lösung (wenn möglich) wäre, den dynamischen Typ insgesamt zu verhindern.
Zyphrax
59
@Zyphrax, nein, es ist noch eine Besetzung erforderlich, für Tdie keine verfügbar ist.
Nawfal
4
Ich weiß, dass das resultierende Objekt wirklich vom Typ ist, Taber Sie erhalten trotzdem nur ein objectals Referenz. hmm, ich fand die Frage interessant in der Prämisse, dass OP nur die TypeVariable und keine anderen Informationen hat. Als ob die Methodensignatur wäre Convert(object source, Type destination):) Trotzdem bekomme ich ur point
nawfal
10
Wie ist das eine Lösung für diese Frage? Ich habe das gleiche Problem und ich habe kein generisches <T>. Ich habe nur eine Typvariable.
Nuri Tasdemir
114

Andere Antworten erwähnen nicht den "dynamischen" Typ. Um eine weitere Antwort hinzuzufügen, können Sie das resultierende Objekt mit dem Typ "dynamisch" speichern, ohne das konvertierte Objekt mit einem statischen Typ umwandeln zu müssen.

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

Beachten Sie, dass der Compiler bei Verwendung von "dynamic" die statische Typprüfung umgeht, die zu möglichen Laufzeitfehlern führen kann, wenn Sie nicht vorsichtig sind.

maulik13
quelle
19
Dies ist die richtige Antwort. Ohne das dynamische Schlüsselwort ist typeof (changedObj) "Objekt". Mit dem dynamischen Schlüsselwort funktioniert es einwandfrei und typeof (changedObject) spiegelt korrekt den gleichen Typ wie typeVar wider. Außerdem müssen Sie nicht (T) wirken, was Sie nicht tun können, wenn Sie den Typ nicht kennen.
Rushinge
5
Ich habe die Ausnahme "Objekt muss IConvertible implementieren", wenn ich diese Lösung verwende. Irgendeine Hilfe?
Nuri Tasdemir
@NuriTasdemir Schwer zu sagen, aber ich glaube, dass die Konvertierung, die Sie durchführen, ohne IConvertible nicht möglich ist. Welche Arten sind an Ihrer Konvertierung beteiligt?
maulik13
Während dies funktioniert, gibt es einen Leistungsverlust bei der Verwendung von Dynamik. Ich würde empfehlen, sie nicht zu verwenden, es sei denn, Sie arbeiten mit anderen Laufzeiten (wofür die Dynamik entwickelt wurde).
Bolo
19

Hier ist meine Methode, um ein Objekt umzuwandeln, aber nicht in eine generische Typvariable, sondern in eine System.Typedynamische:

Ich erstelle zur Laufzeit einen Lambda-Ausdruck mit dem System.Linq.ExpressionsTyp Func<object, object>, der seine Eingabe entpackt, die gewünschte Typkonvertierung durchführt und dann das Ergebnis in einem Feld ausgibt. Ein neuer wird nicht nur für alle Typen benötigt, in die umgewandelt wird, sondern auch für die Typen, die umgewandelt werden (aufgrund des Unboxing-Schritts). Das Erstellen dieser Ausdrücke ist aufgrund der Reflexion, der Kompilierung und der dynamischen Methodenerstellung, die unter der Haube durchgeführt wird, sehr zeitaufwändig. Glücklicherweise können die Ausdrücke nach dem Erstellen wiederholt und ohne hohen Overhead aufgerufen werden, sodass ich jeden einzelnen zwischenspeichere.

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);
}

Beachten Sie, dass dies keine Magie ist. Das Casting erfolgt nicht wie beim Schlüsselwort im Code, sondern dynamicnur die zugrunde liegenden Daten des Objekts. Bei der Kompilierung müssen wir immer noch genau herausfinden, um welchen Typ es sich bei unserem Objekt handelt, was diese Lösung unpraktisch macht. Ich habe dies als Hack geschrieben, um Konvertierungsoperatoren aufzurufen, die durch beliebige Typen definiert sind, aber vielleicht kann jemand da draußen einen besseren Anwendungsfall finden.

balage
quelle
2
Benötigtusing System.Linq.Expressions;
Aaron D
4
Für mich hat dies das gleiche Problem wie die Antwort von Zyphrax. Ich kann keine Methoden für das zurückgegebene Objekt aufrufen, da es immer noch vom Typ "Objekt" ist. Unabhängig davon, ob ich seine Methode ("a" unten) oder Ihre Methode ("b" unten) verwende, erhalte ich den gleichen Fehler bei der (t) Besetzung - "'t' ist eine Variable, wird aber wie ein Typ verwendet.Type t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
muusbolla
@muusbolla Zyphrax 'ursprüngliche Antwort verwendet Generika und Typvariablen, nicht Type. Sie können nicht mit normaler Casting-Syntax umwandeln, wenn Sie nur das Type-Objekt haben. Wenn Sie das Objekt zur Kompilierungszeit als Typ T und nicht zur Laufzeit verwenden möchten, müssen Sie es mit einer Typvariablen oder nur mit dem tatsächlichen Typnamen umwandeln. Ersteres können Sie mit der Antwort von Zaphrax tun.
Ashley
8

Wenn Sie das Ein- und Auspacken der Einfachheit halber beiseite lassen, ist beim Casting entlang der Vererbungshierarchie keine bestimmte Laufzeitaktion erforderlich. Es ist meistens eine Sache zur Kompilierungszeit. Im Wesentlichen weist eine Umwandlung den Compiler an, den Wert der Variablen als einen anderen Typ zu behandeln.

Was könntest du nach der Besetzung tun? Sie kennen den Typ nicht und können daher keine Methoden dafür aufrufen. Es würde nichts Besonderes geben, was du tun könntest. Insbesondere kann es nur nützlich sein, wenn Sie die möglichen Typen zur Kompilierungszeit kennen, sie manuell umwandeln und jeden Fall separat mit ifAnweisungen behandeln:

if (type == typeof(int)) {
    int x = (int)obj;
    DoSomethingWithInt(x);
} else if (type == typeof(string)) {
    string s = (string)obj;
    DoSomethingWithString(s);
} // ...
Mehrdad Afshari
quelle
1
Könnten Sie das bitte in Bezug auf meine Frage klarer erklären?
Theringostarrs
Ich versuche zu erklären, was Sie danach tun könnten. Sie können nicht viel tun, da der C # -Compiler statische Eingaben erfordert, um eine nützliche Sache mit dem Objekt tun zu können
Mehrdad Afshari
Du hast recht. Ich kenne die erwarteten Typen von zwei Variablen, die als Typ 'Objekt' an die Methode gesendet werden. Ich möchte die erwarteten Typen, die in Variablen gespeichert sind, umwandeln und zur Sammlung hinzufügen. Es ist viel einfacher, nach Typ zu verzweigen und einen normalen Cast- und Fangfehler zu versuchen.
Theringostarrs
4
Ihre Antwort ist gut, aber um nicht wählerisch zu sein, stelle ich fest, dass Casts niemals Variablen beeinflussen . Es ist niemals legal, eine Variable in eine Variable eines anderen Typs umzuwandeln. Variablentypen sind in C # unveränderlich. Sie können den in der Variablen gespeicherten Wert nur in einen anderen Typ umwandeln.
Eric Lippert
Ändert die Einführung der dynamischen Typisierung in C # 4.0 diese Antwort?
Daniel T.
6

Wie konntest du das tun? Sie benötigen eine Variable oder ein Feld vom Typ T, in dem Sie das Objekt nach der Umwandlung speichern können. Wie können Sie jedoch eine solche Variable oder ein solches Feld haben, wenn Sie T nur zur Laufzeit kennen? Also, nein, das ist nicht möglich.

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?
Daniel Brückner
quelle
3
Wenn Sie eine generische Klasse verwenden, die eine Methode mit einem Rückgabewert vom Typ T definiert, müssen Sie dies möglicherweise tun. ZB eine Zeichenfolge auf eine Instanz von T analysieren und diese zurückgeben.
Oliver Friedrich
7
Dies ist zum Glück nicht die richtige Antwort. Siehe die Antwort von maulik13.
Rushinge
3
Wo im Namen des Himmels finden Sie eine CastToMethode Object?
ProfK
3

Wenn es um das Casting nach Enum-Typ geht:

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

        return null;
    }

Und du wirst es so nennen:

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

Dies war für mich wichtig, wenn ich den Beschreibungsattributwert mehrerer Aufzählungstypen durch den int-Wert erhalten wollte:

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();
    }

und dann:

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

Alternativ (besserer Ansatz) könnte ein solches Casting so aussehen:

 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));
    }
krzyski
quelle
1

Nachdem ich bei der Verwendung der Antwort von Zyphrax (mit Ausnahme der Implementierung der Schnittstelle) keine Ausnahme gefunden hatte, um das Problem zu umgehen, versuchte ich etwas Unkonventionelles und arbeitete für meine Situation.

Verwenden des Newtonsoft.Json-Nuget-Pakets ...

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

Schaden, das Problem ist, dass Sie kein T haben.

Sie haben nur eine Typvariable.

Tipp an MS, wenn Sie so etwas tun könnten

TryCast<typeof(MyClass)>

wenn würde alle unsere Probleme lösen.

user2825546
quelle
0

Ich werde nie verstehen, warum Sie bis zu 50 Ruf benötigen, um einen Kommentar zu hinterlassen, aber ich musste nur sagen, dass die Antwort von @Curt genau das ist, wonach ich gesucht habe und hoffentlich jemand anderes.

In meinem Beispiel habe ich ein ActionFilterAttribute, mit dem ich die Werte eines JSON-Patchdokuments aktualisiert habe. Ich wusste nicht, was das T-Modell für das Patch-Dokument war, um es zu einem einfachen JsonPatchDocument zu serialisieren und zu deserialisieren, es zu ändern, und dann, weil ich den Typ hatte, es wieder zu dem Typ zu serialisieren und zu deserialisieren.

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 );
Leye Eltee Taiwo
quelle
-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;
}
user2008563
quelle
2
Könnten Sie bitte darauf hinweisen, wie sich diese Antwort von den anderen Antworten unterscheidet und wo diese Lösung angemessen ist?
Klaus Gütter
-2

noch sauberer:

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

        t = (T)o;
        return true;
    }
Harm Salomons
quelle
-2

Wenn Sie Objekte zur Laufzeit umwandeln müssen, ohne den Zieltyp zu kennen, können Sie mithilfe der Reflektion einen dynamischen Konverter erstellen.

Dies ist eine vereinfachte Version (ohne generierte Caching-Methode):

    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 });
            }
    }

dann kannst du es nennen:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));
Marianop
quelle