Laden...
FAQ

[FAQ] Casten aber richtig: Boxing/Unboxing - () / is / as / Pattern Matching

Erstellt von Programmierhans vor 17 Jahren Letzter Beitrag vor 3 Jahren 39.251 Views
Programmierhans Themenstarter:in
4.221 Beiträge seit 2005
vor 17 Jahren
[FAQ] Casten aber richtig: Boxing/Unboxing - () / is / as / Pattern Matching

Kurz und bündig zusammengefasst die verschiedenen Varianten von einem Type in einen anderen zu casten.

Es gibt verschiedene Varianten:

Direct-Cast:
-Harter Cast kann InvalidCastException auslösen wenn der Type nicht passt:
-Zugriff auf die Zielvariable darf erst nach einer Prüfung auf null stattfinden (c könnte ja null sein)

As-Cast:
-Weicher Cast mit Typenabfrage: Löst keine InvalidCastException aus wenn der Type nicht passt.
-Zugriff auf die Zielvariable darf erst nach einer Prüfung auf null stattfinden (c könnte ja null sein)

Sichere Varianten

mittels is:
-Prüfung auf korrekten Typen (null is TextBox ergibt = false)
-Zugriff auf die Zielvariable sofort möglich

mittels as:
-Prüfung auf null und auf korrekten Typen
-Zugriff auf die Zielvariable sofort möglich
-Insgesamt weniger Effizient als die is-Variante
[Edit]
-spart den is-Zugriff
[/Edit]



foreach (Control c in this.Controls)
{
	//Direct-Cast (es kracht wenn c nicht TextBox ist):
	// und es kracht beim Zugriff auf tDirect wenn c null ist.
	TextBox tDirect=(TextBox)c;

	//As-Cast es kracht beim Zugriff auf tAS wenn c null ist oder c keine Textbox ist (dann ist tAs auch null).
	TextBox tAs=c as TextBox;

	//der As-Cast macht intern so was:
	//es kracht beim Zugriff auf tAS wenn c null ist.
	TextBox tIs=c is TextBox?(TextBox)c:null;


	//und so ist die sichere Variante mit is:
	if(c is TextBox)
	{
		TextBox tSave=(TextBox)c;
		//so ist tSave sicher nie null
	}
	
	//und so eine sichere Variante mit as
	TextBox tAsSave=c as TextBox;
	if (tAsSave!=null)
	{
		//so ist tAsSave sicher nie null
	}
}


Edit: Is-Variante gekürzt:

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

16.806 Beiträge seit 2008
vor 3 Jahren

Nach fast 14 Jahren des initialen Beitrags hat sich in C# doch einiges geändert.

Boxing und Unboxing

Boxing ist die Art und Weise wie mit Typen in C# umgegangen wird.
Die Konvertierung von einem konkreten Value-Type (zB int) in den Typ object nennt sich Boxing.

int i = 123;
// boxing von i auf o
object o = i;

Das Gegenteil von Boxing ist das Unboxing; hier erfolgt die Konvertierung von object auf den jeweiligen Value-Type.


object o = 123;
int i = (int)o;  // unboxing

Achtung: Beim Unboxing muss beachtet werden, dass der ursprüngliche Value-Type verwendet wird, ansonsten quittiert das die Runtime mit einer InvalidCastException.


// Funktioniert:
int i1 = 123;
object o = i1;
int i2 = (int)o;

// InvalidCastException:
int i1 = 123;
object o = i1;
uint i2 = (uint)o; // der Ursprung ist int und nicht uint!

Mehr Informationen in der Dokumentation von Boxing und Unboxing.

Unsicheres Casten aka hartes Konvertieren

Das unsichere Konvertieren wirft eine InvalidCastException, wenn der Ziel-Type nicht passt.


object o = "Hallo";
string s = (string)o; // funktioniert
int wert = (int)o; // InvalidCastException

Sicheres Casten mit dem as-Operator

Aufgrund der potentiellen InvalidCastException gab es seit Beginn von C# auch den as-Operator, mit dem ein sicheres Konvertieren möglich ist - allerdings nur für Referenztypen.

Sofern der Type nicht identisch ist wird keine Exception geworfen, sondern null gesetzt.

object s = "Hallo";
MyClass myClassInstance = s as MyClass;

if (myClassInstance == null)
{
    // Konvertierung fehlgeschlagen
}
else
{
    // Konvertierung war erfolgreich
}

Mehr Informationen in der Dokumentation von as-Operator.

Sicheres Casten mit dem is-Operator

Der as-Operator funktioniert nur mit Referenztypen; daher nicht mit int und Co. Die Alternative an dieser Stelle ist der is-Operator.

Mit dem is-Operator kann vor dem Konvertieren geprüft werden, ob der Typ passt:

int wert;
if (s is int)
{
     wert = (int)s;
}
else
{
    // Es kann nicht konvertiert werden, weil s kein int ist.
}

Der is-Operator kann mit Wert- und Referenztypen verwendet werden.

Mehr Informationen in der Dokumentation von is-Operator.

Pattern Matching mit is - Type Pattern

Mit C# 7 wurde das Pattern Matching eingeführt. Pattern Matching fällt unter die Kategorie Syntaxzucker und verkürzt die Schreibweise.
Im Falle des Pattern Matching mit dem is-Operator kann dabei die Prüfung und das Casting in einen Befehl verkürzt werden.


// Ohne Pattern Matching:
Person p1 = new Person();
object o = p1;

Person p2;
if (o is Person)
{
    p2 = (Person)o;

   // p2 ist nun eine Instanz von Person
}

// Mit Pattern Matching:
Person p1 = new Person();
object o = p1;

if (o is Person p2)
{
   // p2 ist nun eine Instanz von Person
}

Das Pattern Matching an dieser Stelle funktioniert auch mit var:


    Person p1 = new Person();
    object o = p1;

    if (o is var v)
    {
        Console.WriteLine($"var ist vom Typ: {v.GetType().Name}"); // Ausgabe: var ist vom Typ: Person
    }

Achtung: aus Compiler-Sicht ist var vom Typ object; damit stehen die Eigenschaften nur zur Laufzeit zur Verfügung!

Mehr Informationen in der Dokumentation von Type Testing mit Pattern Matching.

Operatoren

Die Umwandlung von int in uint ist streng genommen keine Konvertierung, sondern eine Umwandlung; im Englischen als _reassigment _bezeichnet.
Dass im Sprachgebrauch von C# trotzdem damit umgegangen werden kann als sei es eine Konvertierung, können Operatoren verwendet werden.
Ein solcher explziter Operator ist in diesem Fall für die Umwandlung von int in uint verantwortlich:

public static explicit operator int(uint value) => Convert.ToInt32(value);

int i = 1;
uint u = (uint)i; // die harte Umwandlung mit (int) stellt die explizite Konvertierung dar

Neben der expliziten Konvertierung, die das Voranstellen des Zieltyps erfordert, gibt es auch die implizite Konvertierung.

Hierbei erkennt der Compiler automatisch den entsprechenden Operator, um die Konvertierung durchzuführen. Ist kein Operator deklariert wird ein Compiler-Error generiert, der beklagt, dass nur eine explizite und keine implizite Konvertierung gefunden wurde.

public class MyCustomInteger
{
    public int Value { get; }

    public MyCustomInteger(int value)
    {
        Value = value;
    }

    public static explicit operator MyCustomInteger(int value) => new MyCustomInteger(value);
    // Verwendung:
    MyCustomInteger myValue = (MyCustomInteger)1;

    // oder

    public static implicit operator MyCustomInteger(int value) => new MyCustomInteger(value);
    // Verwendung:
    MyCustomInteger myValue = 1;
}

Achtung: es können nicht beide Operator-Arten mit den gleichen Parameter gleichzeitig deklariert werden.

Mehr Informationen in der Dokumentation von Benutzerdefinierte Konvertierungsoperatoren.