406 Stimmen

Casting vs. Verwendung des Schlüsselworts "as" in der CLR

Bei der Programmierung von Schnittstellen habe ich festgestellt, dass ich viel mit Casting oder Objekttypkonvertierung zu tun habe.

Gibt es einen Unterschied zwischen diesen beiden Umrechnungsmethoden? Wenn ja, gibt es einen Kostenunterschied oder wie wirkt sich das auf mein Programm aus?

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

Und was ist "im Allgemeinen" die bevorzugte Methode?

540voto

Jon Skeet Punkte 1325502

Die Antwort unter dieser Zeile stammt aus dem Jahr 2008.

Mit C# 7 wurde der Musterabgleich eingeführt, der weitgehend die as Operator, wie Sie jetzt schreiben können:

if (randomObject is TargetType tt)
{
    // Use tt here
}

Beachten Sie, dass tt ist danach immer noch im Geltungsbereich, aber nicht endgültig zugewiesen. (Es est definitiv zugewiesen innerhalb der if Körper). Das ist in manchen Fällen etwas lästig, wenn Sie also wirklich Wert darauf legen, die kleinstmögliche Anzahl von Variablen in jedem Bereich einzuführen, sollten Sie vielleicht trotzdem is gefolgt von einem Gips.


Ich glaube nicht, dass in den bisherigen Antworten (zum Zeitpunkt des Beginns dieser Antwort!) wirklich erklärt wurde, wo es sich lohnt, was zu verwenden.

  • Tun Sie das nicht:

    // Bad code - checks type twice for no reason
    if (randomObject is TargetType)
    {
        TargetType foo = (TargetType) randomObject;
        // Do something with foo
    }

    Dabei wird nicht nur zweimal geprüft, sondern es werden möglicherweise auch verschiedene Dinge geprüft, wenn randomObject ein Feld und nicht eine lokale Variable ist. Es ist möglich, dass die "if"-Funktion erfolgreich ist, aber der Cast fehlschlägt, wenn ein anderer Thread den Wert von randomObject zwischen den beiden.

  • Wenn randomObject wirklich sollte ein Fall sein von TargetType Wenn das nicht der Fall ist, bedeutet das, dass es einen Fehler gibt, dann ist Gießen die richtige Lösung. Dadurch wird sofort eine Ausnahme ausgelöst, was bedeutet, dass keine weitere Arbeit unter falschen Annahmen durchgeführt wird und die Ausnahme den Fehlertyp korrekt anzeigt.

    // This will throw an exception if randomObject is non-null and
    // refers to an object of an incompatible type. The cast is
    // the best code if that's the behaviour you want.
    TargetType convertedRandomObject = (TargetType) randomObject;
  • Wenn randomObject puede ein Fall sein von TargetType y TargetType ein Referenztyp ist, dann verwenden Sie einen Code wie diesen:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
  • Wenn randomObject puede ein Fall sein von TargetType y TargetType ein Wertetyp ist, dann können wir nicht as con TargetType selbst, aber wir können einen nullbaren Typ verwenden:

    TargetType? convertedRandomObject = randomObject as TargetType?;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject.Value
    }

    (Anmerkung: Derzeit ist dies tatsächlich langsamer als ist + gegossen . Ich denke, es ist eleganter und konsistenter, aber so ist es eben).

  • Wenn Sie den umgewandelten Wert nicht wirklich benötigen, sondern nur wissen wollen, ob er est eine Instanz von TargetType, dann wird die is Betreiber ist Ihr Freund. In diesem Fall spielt es keine Rolle, ob TargetType ein Referenztyp oder ein Wertetyp ist.

  • Es kann andere Fälle geben, in denen Generika is ist nützlich (weil man vielleicht nicht weiß, ob T ein Referenztyp ist oder nicht, so dass man as nicht verwenden kann), aber sie sind relativ undurchsichtig.

  • Ich habe fast sicher verwendet is für den Fall des Werttyps, da ich nicht daran gedacht habe, einen nullbaren Typ zu verwenden und as zusammen :)


EDIT: Beachten Sie, dass keine der oben genannten spricht über Leistung, andere als der Wert-Typ-Fall, wo ich festgestellt habe, dass unboxing zu einem nullable Wert-Typ tatsächlich langsamer ist - aber konsistent.

Wie in der Antwort von naasking beschrieben, sind is-and-cast oder is-and-as mit modernen JITs genauso schnell wie as-and-null-check, wie der folgende Code zeigt:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "x";
            values[i + 2] = new object();
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);
    }

    static void FindLengthWithIsAndCast(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string) o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Auf meinem Laptop werden sie alle in etwa 60 ms ausgeführt. Zwei Dinge sind zu beachten:

  • Es gibt keinen signifikanten Unterschied zwischen ihnen. (In der Tat gibt es Situationen, in denen die as-plus-null-Prüfung definitiv est langsamer. Der obige Code macht die Typprüfung tatsächlich einfach, weil er für eine versiegelte Klasse ist; wenn Sie für eine Schnittstelle prüfen, kippt das Gleichgewicht leicht zugunsten der as-plus-null-Prüfung).
  • Sie sind alle wahnsinnig schnell. Das ist einfach wird nicht der Engpass in Ihrem Code sein, es sei denn, Sie haben wirklich nicht vor, die alles mit den Werten danach.

Machen wir uns also keine Sorgen um die Leistung. Sorgen wir uns lieber um die Korrektheit und Konsistenz.

Ich behaupte, dass is-and-cast (oder is-and-as) beide unsicher sind, wenn es um Variablen geht, da sich der Typ des Wertes, auf den er sich bezieht, aufgrund eines anderen Threads zwischen dem Test und dem Cast ändern kann. Das wäre eine ziemlich seltene Situation - aber ich möchte lieber eine Konvention haben, die ich konsequent verwenden kann.

Ich behaupte auch, dass die as-then-null-Prüfung eine bessere Trennung der Anliegen ermöglicht. Wir haben eine Anweisung, die eine Umwandlung versucht, und dann eine Anweisung, die das Ergebnis verwendet. Der is-and-cast oder is-and-as führt einen Test durch und dann ein weiterer Versuch, den Wert zu konvertieren.

Mit anderen Worten, würde jemand immer schreiben:

int value;
if (int.TryParse(text, out value))
{
    value = int.Parse(text);
    // Use value
}

Das ist in etwa das, was is-and-cast macht - wenn auch offensichtlich auf eine etwas billigere Weise.

77voto

Patrick Desjardins Punkte 130529

"als" gibt NULL zurück, wenn die Besetzung nicht möglich ist.

Gießen vor löst eine Ausnahme aus.

Für die Leistung ist das Auslösen einer Ausnahme in der Regel zeitaufwändiger.

30voto

Chris S Punkte 63542

Hier ist eine weitere Antwort mit einem IL-Vergleich. Betrachten Sie die Klasse:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 

    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

Schauen Sie sich nun die IL an, die jede Methode erzeugt. Auch wenn Ihnen die Op-Codes nichts sagen, können Sie einen großen Unterschied sehen - isinst wird aufgerufen, gefolgt von castclass in der DirectCast-Methode. Also im Grunde zwei Aufrufe statt einem.

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

Das Schlüsselwort isinst versus castclass

Dieser Blogbeitrag hat einen guten Vergleich zwischen den beiden Vorgehensweisen. Seine Zusammenfassung lautet:

  • Im direkten Vergleich ist isinst schneller als castclass (wenn auch nur geringfügig)
  • Bei der Durchführung von Prüfungen, um sicherzustellen, dass die Konvertierung erfolgreich war, war isinst deutlich schneller als castclass
  • Eine Kombination aus isinst und castclass sollte nicht verwendet werden, da diese weitaus langsamer war als die schnellste "sichere" Umwandlung (über 12 % langsamer).

Ich persönlich verwende immer As, weil es einfach zu lesen ist und vom .NET-Entwicklungsteam empfohlen wird (oder zumindest von Jeffrey Richter)

18voto

Patrik Hägne Punkte 15961

Einer der subtileren Unterschiede zwischen den beiden ist, dass das "as"-Schlüsselwort nicht zum Casting verwendet werden kann, wenn ein Cast-Operator beteiligt ist:

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

Dies wird nicht kompiliert (obwohl ich glaube, dass es in früheren Versionen) auf der letzten Zeile, da die "as"-Schlüsselwörter nicht berücksichtigen, cast-Operatoren. Die Zeile string cast = (string)f; funktioniert aber einwandfrei.

12voto

Anton Gogolev Punkte 109749

als löst nie eine Ausnahme aus, wenn es die Konvertierung nicht durchführen kann und null stattdessen ( als funktioniert nur bei Referenztypen). Also mit als ist grundsätzlich gleichwertig mit

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

C-Stil Casts, auf der anderen Seite, werfen eine Ausnahme, wenn keine Umwandlung möglich ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X