Casting vs mit dem Schlüsselwort 'as' in der CLR

387

Beim Programmieren von Schnittstellen habe ich festgestellt, dass ich viel Casting oder Objekttypkonvertierung mache.

Gibt es einen Unterschied zwischen diesen beiden Konvertierungsmethoden? Wenn ja, gibt es einen Kostenunterschied oder wie wirkt sich dies 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);
    }
}

Was ist "im Allgemeinen" die bevorzugte Methode?

Frank V.
quelle
Könnten Sie der Frage ein kleines Beispiel hinzufügen, warum Sie die Casts überhaupt verwenden, oder vielleicht ein neues beginnen? Ich bin ein bisschen daran interessiert, warum Sie die Besetzung nur für Unit-Tests benötigen würden. Ich denke, es liegt jedoch außerhalb des Rahmens dieser Frage.
Erik van Brakel
2
Ich kann wahrscheinlich meinen Komponententest ändern, um dies zu vermeiden. Grundsätzlich läuft es darauf hinaus, dass ich eine Eigenschaft für mein konkretes Objekt habe, die sich nicht in der Schnittstelle befindet. Ich muss diese Eigenschaft festlegen, aber im wirklichen Leben wäre diese Eigenschaft auf andere Weise festgelegt worden. Beantwortet das deine Frage?
Frank V
Als Patrik Hagne astutely unten weist darauf hin, es IST ein Unterschied.
Neil

Antworten:

519

Die Antwort unter der Zeile wurde 2008 geschrieben.

C # 7 führte den Mustervergleich ein, der den asOperator weitgehend ersetzt hat , wie Sie jetzt schreiben können:

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

Beachten Sie, dass dies danach ttnoch im Geltungsbereich liegt, aber nicht definitiv zugewiesen ist. (Es ist definitiv im ifKörper zugeordnet.) Das ist in einigen Fällen etwas ärgerlich. Wenn Sie also wirklich daran interessiert sind, die kleinstmögliche Anzahl von Variablen in jedem Bereich einzuführen, möchten Sie möglicherweise immer noch iseine Besetzung verwenden, gefolgt von einer Besetzung.


Ich glaube, keine der bisherigen Antworten (zum Zeitpunkt des Beginns dieser Antwort!) Hat wirklich erklärt, wo es sich lohnt, welche zu verwenden.

  • Tu das nicht:

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

    Dies wird nicht nur zweimal überprüft, sondern es können auch verschiedene Dinge überprüft werden, wenn randomObjectes sich eher um ein Feld als um eine lokale Variable handelt. Es ist möglich, dass das "Wenn" bestanden wird, aber dann die Umwandlung fehlschlägt, wenn ein anderer Thread den Wert randomObjectzwischen den beiden ändert .

  • Wenn randomObjectwirklich soll eine Instanz sein TargetType, das heißt , wenn es nicht ist , das bedeutet ein Fehler da ist, dann ist Gießen die richtige Lösung. Das löst sofort eine Ausnahme aus, was bedeutet, dass unter falschen Annahmen keine Arbeit mehr ausgeführt wird und die Ausnahme die Art des Fehlers 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 es sich randomObject möglicherweise um eine Instanz von TargetTypeund TargetTypeum einen Referenztyp handelt, verwenden Sie folgenden Code:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
  • Wenn es sich randomObject möglicherweise um eine Instanz von TargetTypeund TargetTypeum einen Werttyp handelt, können wir ihn nicht asmit sich TargetTypeselbst verwenden, aber wir können einen nullbaren Typ verwenden:

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

    (Hinweis: Derzeit ist dies tatsächlich langsamer als + Besetzung . Ich denke, es ist eleganter und konsistenter, aber los geht's.)

  • Wenn Sie den konvertierten Wert wirklich nicht benötigen, aber nur wissen müssen, ob es sich um eine Instanz von TargetType handelt, ist der isOperator Ihr Freund. In diesem Fall spielt es keine Rolle, ob TargetType ein Referenztyp oder ein Werttyp ist.

  • Es kann andere Fälle mit Generika geben, in denen dies isnützlich ist (weil Sie möglicherweise nicht wissen, ob T ein Referenztyp ist oder nicht, Sie können es also nicht als verwenden), aber sie sind relativ dunkel.

  • Ich habe mit ziemlicher Sicherheit schon früher isfür den Werttyp-Fall verwendet, ohne daran gedacht zu haben, einen nullbaren Typ zu verwenden und aszusammen :)


BEARBEITEN: Beachten Sie, dass keiner der oben genannten Punkte die Leistung betrifft, außer dem Fall des Werttyps, bei dem ich festgestellt habe, dass das Entpacken in einen nullbaren Werttyp tatsächlich langsamer ist - aber konsistent.

Gemäß der Antwort von naasking sind is-and-cast oder is-and-as mit modernen JITs so schnell wie eine Nullprüfung, 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 diese alle in ca. 60 ms ausgeführt. Zwei Dinge zu beachten:

  • Es gibt keinen signifikanten Unterschied zwischen ihnen. (In der Tat gibt es Situationen , in denen der als-plus-null-Check auf jeden Fall ist langsamer Der obige Code macht eigentlich die Art Prüfung einfach , weil es für eine versiegelte Klasse ist;. , Wenn Sie überprüfen für eine Schnittstelle, leicht die Balance - Tipps zugunsten von as-plus-null-check.)
  • Sie sind alle wahnsinnig schnell. Dies ist einfach nicht der Engpass in Ihrem Code, es sei denn, Sie werden danach wirklich nichts mehr mit den Werten tun .

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

Ich behaupte, dass is-and-cast (oder is-and-as) beim Umgang mit Variablen beide unsicher sind, 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 hätte lieber eine Konvention, die ich konsequent anwenden kann.

Ich behaupte auch, dass der As-Then-Null-Check eine bessere Trennung der Bedenken ermöglicht. Wir haben eine Anweisung, die eine Konvertierung versucht, und dann eine Anweisung, die das Ergebnis verwendet. Das is-and-cast oder is-and-as führt einen Test und dann einen weiteren Versuch durch, den Wert zu konvertieren.

Anders ausgedrückt, würde jemand jemals schreiben:

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

Das ist genau das, was Is-and-Cast tut - wenn auch offensichtlich billiger.

Jon Skeet
quelle
7
Hier sind die Kosten für is / as / Casting in Bezug auf IL: atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
Sockel
3
Wenn targetObject ein Zieltyp sein könnte , warum wird die Verwendung der Kombination "is" und cast als schlechte Vorgehensweise angesehen? Ich meine, es wird ein langsamerer Code generiert, aber in diesem Fall sind die Absichten klarer als die AS-Umwandlung, z. B. "Etwas tun, wenn targetObject targetType ist" anstelle von "Etwas tun, wenn targetObject null ist". Außerdem erstellt die AS-Klausel eine unnötige Variable außerhalb des IF-Bereichs.
Valera Kolupaev
2
@Valera: Gute Punkte, obwohl ich vorschlagen würde, dass der as / null-Test so idiomatisch ist, dass die Absicht fast allen C # -Entwicklern klar sein sollte. Ich persönlich mag die Vervielfältigung in der Besetzung nicht. Ich hätte eigentlich gerne eine Art "Als-ob" -Konstrukt, das beide Aktionen in einer ausführt. Sie gehen so oft zusammen ...
Jon Skeet
2
@ Jon Skeet: Entschuldigung für meine Verspätung. Is And Cast: 2135, Is And As: 2145, As And Null Check: 1961, Spezifikationen: Betriebssystem: Windows Seven, CPU: i5-520M, 4 GB DDR3 1033 RAM, Benchmark auf Array von 128.000.000 Artikeln.
Behrooz
2
Mit C # 7 können Sie tun: if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}oder verwenden switch/ case sehen docs
WerWet
72

"as" gibt NULL zurück, wenn keine Umwandlung möglich ist.

Das vorherige Casting löst eine Ausnahme aus.

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

Patrick Desjardins
quelle
3
Ausnahme Anhebung ist teurer, aber wenn Sie wissen , dass das Objekt richtig gegossen werden kann, wie benötigt mehr Zeit , weil der Sicherheitsüberprüfung (siehe Anton Antwort). Die Kosten für die Sicherheitsüberprüfung sind jedoch meines Erachtens recht gering.
17
Die Kosten für die potenzielle Auslösung einer Ausnahme sind ein zu berücksichtigender Faktor, aber häufig das richtige Design.
Jeffrey L Whitledge
@panesofglass - Bei Referenztypen wird die Konvertierungskompatibilität zur Laufzeit sowohl für as als auch für cast immer überprüft, sodass der Faktor nicht zwischen den beiden Optionen unterscheidet. (Wenn dies nicht so wäre, könnte die Besetzung keine Ausnahme
auslösen
4
@Frank - Wenn Sie beispielsweise eine Sammlung vor Generika verwenden müssen und eine Methode in Ihrer API eine Liste von Mitarbeitern erfordert und ein Joker stattdessen eine Liste von Produkten übergibt, kann eine ungültige Besetzungsausnahme zum Signalisieren geeignet sein die Verletzung der Schnittstellenanforderungen.
Jeffrey L Whitledge
27

Hier ist eine andere 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. Selbst wenn die Op-Codes für Sie nichts bedeuten, können Sie einen großen Unterschied feststellen: isinst wird aufgerufen, gefolgt von castclass in der DirectCast-Methode. Also grundsätzlich zwei Anrufe statt eines.

.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 im Vergleich zur castclass

Dieser Blog-Beitrag hat einen anständigen Vergleich zwischen den beiden Möglichkeiten. Seine Zusammenfassung lautet:

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

Ich persönlich benutze immer As, weil es leicht zu lesen ist und vom .NET-Entwicklungsteam (oder Jeffrey Richter sowieso) empfohlen wird.

Chris S.
quelle
Ich suchte nach einer klaren Erklärung für Casting vs as. Diese Antwort macht es viel klarer, da es sich um eine schrittweise Erklärung der gemeinsamen Zwischensprache handelt. Vielen Dank!
Morse
18

Einer der subtileren Unterschiede zwischen den beiden besteht darin, dass das Schlüsselwort "as" nicht für das 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 in der letzten Zeile nicht kompiliert (obwohl ich denke, dass dies in früheren Versionen der Fall war), da die Schlüsselwörter "as" Cast-Operatoren nicht berücksichtigen. Die Linie string cast = (string)f;funktioniert aber gut.

Patrik Hägne
quelle
12

as löst niemals eine Ausnahme aus, wenn die Konvertierung nicht durchgeführt werden kann und stattdessen null zurückgegeben wird ( wie nur bei Referenztypen). Die Verwendung von as entspricht also im Grunde genommen

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

C-artige Casts hingegen lösen eine Ausnahme aus, wenn keine Konvertierung möglich ist.

Anton Gogolev
quelle
4
Äquivalent, ja, aber nicht dasselbe. Dies erzeugt viel mehr Code als.
Sockel
10

Nicht wirklich eine Antwort auf Ihre Frage, aber was ich denke, ist ein wichtiger verwandter Punkt.

Wenn Sie auf eine Schnittstelle programmieren, sollten Sie keine Besetzung benötigen. Hoffentlich sind diese Darsteller sehr selten. Wenn nicht, müssen Sie wahrscheinlich einige Ihrer Schnittstellen überdenken.

Kröte
quelle
Das Casting wurde bisher hauptsächlich für meine Unit-Tests benötigt, aber ich danke Ihnen, dass Sie es angesprochen haben. Ich werde das im Hinterkopf behalten, während ich daran arbeite.
Frank V
Ich bin mit Kröte einverstanden und bin auch neugierig, warum der Aspekt des Komponententests für das Casting für Sie relevant ist. @Frank V. Wenn Casting erforderlich ist, ist häufig ein Redesign oder Refactor erforderlich, da dies darauf hindeutet, dass Sie es versuchen verschiedene Probleme zu lösen, bei denen sie unterschiedlich gehandhabt werden sollten.
Der Senator
@TheSenator Diese Frage ist weit über 3 Jahre alt, daher erinnere ich mich nicht wirklich. Aber ich habe die Schnittstellen wahrscheinlich auch beim Unit-Test aggressiv genutzt. Möglicherweise, weil ich das Factory-Muster verwendet habe und keinen Zugriff auf einen öffentlichen Konstruktor für die zu testenden Zielobjekte hatte.
Frank V
9

Bitte ignorieren Sie den Rat von Jon Skeet, bezüglich: Vermeiden Sie Test-and-Cast-Muster, dh:

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

Die Idee, dass dies mehr kostet als eine Besetzung und ein Null-Test, ist ein Mythos :

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

Es ist eine Mikrooptimierung, die nicht funktioniert. Ich habe einige echte Tests durchgeführt , und Test-and-Cast ist tatsächlich schneller als Cast-and-Null-Vergleich, und es ist auch sicherer, weil Sie nicht die Möglichkeit haben, eine Nullreferenz im Bereich außerhalb des Falls zu haben Scheitern.

Wenn Sie einen Grund suchen, warum Test-and-Cast schneller oder zumindest nicht langsamer ist, gibt es einen einfachen und komplexen Grund.

Einfach: Selbst naive Compiler werden zwei ähnliche Operationen wie Test-and-Cast zu einem einzigen Test und Zweig zusammenführen. Cast-and-Null-Test kann zwei Tests und einen Zweig erzwingen, einen für den Typentest und die Konvertierung in Null bei einem Fehler, einen für die Nullprüfung selbst. Zumindest werden beide auf einen einzigen Test und Zweig optimiert, sodass Test and Cast weder langsamer noch schneller als Cast and Null-Test ist.

Komplex: Warum Test und Cast schneller sind: Cast-and-Null-Test führt eine weitere Variable in den äußeren Bereich ein, die der Compiler auf Lebendigkeit hin verfolgen muss, und kann diese Variable möglicherweise nicht optimieren, je nachdem, wie komplex Ihr Steuerelement ist. Fluss ist. Umgekehrt führt test-and-cast eine neue Variable nur in einem begrenzten Bereich ein, sodass der Compiler weiß, dass die Variable nach dem Beenden des Bereichs tot ist, und so die Registerzuordnung besser optimieren kann.

Bitte lassen Sie diesen "Cast-and-Null-Test ist besser als Test-and-Cast" -Ratschlag DIE. BITTE. Test-and-Cast ist sicherer und schneller.

naasking
quelle
7
@naasking: Wenn Sie zweimal testen (gemäß Ihrem ersten Snippet), besteht die Möglichkeit, dass sich der Typ zwischen den beiden Tests ändert, wenn es sich um ein Feld oder einen refParameter handelt. Es ist sicher für lokale Variablen, aber nicht für Felder. Ich wäre daran interessiert, Ihre Benchmarks auszuführen, aber der Code, den Sie in Ihrem Blog-Beitrag angegeben haben, ist nicht vollständig. Ich bin damit einverstanden, keine Mikrooptimierung vorzunehmen, aber ich denke nicht, dass die zweimalige Verwendung des Werts lesbarer oder eleganter ist als die Verwendung von "as" und eines Nullitätstests. (Ich würde definitiv eine gerade Besetzung anstelle von "wie" nach einem is verwenden, übrigens.)
Jon Skeet
5
Ich verstehe auch nicht, warum es sicherer ist. Ich habe gezeigt, warum es tatsächlich weniger sicher ist. Sicher, Sie erhalten eine Variable im Gültigkeitsbereich, die möglicherweise null ist. Wenn Sie diese jedoch nicht außerhalb des Gültigkeitsbereichs des nachfolgenden "if" -Blocks verwenden, ist dies in Ordnung. Die Sicherheitsbedenken, die ich angesprochen habe (in Bezug auf Felder, deren Wert sich ändert), sind ein echtes Problem mit dem angezeigten Code. Ihre Sicherheitsbedenken erfordern, dass Entwickler in anderen Codes nachlässig sind.
Jon Skeet
1
+1 für den Hinweis, dass / cast oder as / cast in Wirklichkeit nicht langsamer ist, wohlgemerkt. Nachdem ich selbst einen vollständigen Test durchgeführt habe, kann ich bestätigen, dass es meines Erachtens keinen Unterschied macht - und ehrlich gesagt können Sie in sehr kurzer Zeit eine unglaubliche Anzahl von Würfen ausführen . Aktualisiert meine Antwort mit dem vollständigen Code.
Jon Skeet
1
Wenn es sich bei der Bindung nicht um eine lokale Bindung handelt, besteht die Möglichkeit eines TOCTTOU-Fehlers (Check-to-Time-of-Use-Zeit). Warum es sicherer ist, arbeite ich mit vielen Nachwuchsentwicklern zusammen, die aus irgendeinem Grund gerne Einheimische wiederverwenden. cast-and-null ist daher meiner Erfahrung nach eine sehr reale Gefahr, und ich bin nie in eine TOCTTOU-Situation geraten, da ich meinen Code nicht so gestalte. Die Laufzeit-Testgeschwindigkeit ist sogar schneller als der virtuelle Versand [1]! Betreff: Code, ich werde sehen, ob ich die Quelle für den Cast-Test finden kann. [1] Higherlogics.blogspot.com/2008/10/…
Naasking
1
@naasking: Ich bin noch nie auf das Problem der lokalen Wiederverwendung gestoßen - aber ich würde sagen, es ist einfacher, es in der Codeüberprüfung zu erkennen als den subtileren TOCTTOU-Fehler. Es ist auch erwähnenswert, dass ich gerade meine eigene Benchmark-Überprüfung auf Schnittstellen anstelle einer versiegelten Klasse wiederholt habe und dass dies die Leistung zugunsten der Prüfung bis dahin auf Null tippt ... aber wie gesagt, die Leistung ist nicht Deshalb würde ich hier keinen bestimmten Ansatz wählen.
Jon Skeet
4

Wenn die Umwandlung fehlschlägt, löst das Schlüsselwort 'as' keine Ausnahme aus. Stattdessen wird die Variable auf null (oder auf ihren Standardwert für Werttypen) gesetzt.

Der Schlumpf
quelle
3
Keine Standardwerte für Werttypen. Da kann nicht zum Gießen von Werttypen verwendet werden.
Patrik Hägne
2
Das Schlüsselwort 'as' funktioniert bei Werttypen nicht, daher wird es immer auf null gesetzt.
Erik van Brakel
4

Dies ist keine Antwort auf die Frage, sondern ein Kommentar zum Codebeispiel der Frage:

Normalerweise sollten Sie kein Objekt von z. B. IMyInterface in MyClass umwandeln müssen. Das Tolle an Schnittstellen ist, dass Sie sich nicht darum kümmern müssen, welche Art von Objekt Sie erhalten, wenn Sie ein Objekt als Eingabe verwenden, die eine Schnittstelle implementiert.

Wenn Sie IMyInterface in MyClass umwandeln, gehen Sie bereits davon aus, dass Sie ein Objekt vom Typ MyClass erhalten, und es macht keinen Sinn, IMyInterface zu verwenden. Wenn Sie Ihren Code mit anderen Klassen füttern, die IMyInterface implementieren, wird Ihr Code beschädigt ...

Nun mein Rat: Wenn Ihre Schnittstellen gut gestaltet sind, können Sie viel Typografie vermeiden.

f3lix
quelle
3

Der asOperator kann nur für Referenztypen verwendet werden, er kann nicht überladen werden und er wird zurückgegeben, nullwenn der Vorgang fehlschlägt. Es wird niemals eine Ausnahme auslösen.

Casting kann für alle kompatiblen Typen verwendet werden, es kann überladen werden und es wird eine Ausnahme ausgelöst, wenn der Vorgang fehlschlägt.

Die Wahl der Verwendung hängt von den Umständen ab. In erster Linie geht es darum, ob Sie bei einer fehlgeschlagenen Konvertierung eine Ausnahme auslösen möchten.

Jeffrey L Whitledge
quelle
1
'as' kann auch für nullbare Werttypen verwendet werden, was ein interessantes Muster liefert. Siehe meine Antwort für Code.
Jon Skeet
1

Meine Antwort bezieht sich nur auf die Geschwindigkeit in Fällen, in denen wir den Typ nicht überprüfen und nach dem Casting keine Nullen überprüfen. Ich habe Jon Skeets Code um zwei zusätzliche Tests erweitert:

using System;
using System.Diagnostics;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];

        for (int i = 0; i < Size; i++)
        {
            values[i] = "x";
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);

        FindLengthWithCast(values);
        FindLengthWithAs(values);

        Console.ReadLine();
    }

    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);
    }
    static void FindLengthWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = (string)o;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

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

Ergebnis:

Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46

Versuchen Sie nicht, sich (wie ich) auf die Geschwindigkeit zu konzentrieren, da dies alles sehr, sehr schnell ist.

CoperNick
quelle
In ähnlicher Weise stellte ich bei meinen Tests fest, dass die asKonvertierung (ohne Fehlerprüfung) etwa 1-3% schneller lief als das Casting (etwa 540 ms gegenüber 550 ms bei 100 Millionen Iterationen). Weder wird Ihre Bewerbung machen noch brechen.
Palswim
1

Neben all dem, was hier bereits gezeigt wurde, bin ich auf einen praktischen Unterschied gestoßen, den ich für erwähnenswert halte, zwischen explizitem Casting

var x = (T) ...

versus mit dem asOperator.

Hier ist das Beispiel:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GenericCaster<string>(12345));
        Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
        Console.WriteLine(GenericCaster<double>(20.4));

        //prints:
        //12345
        //null
        //20.4

        Console.WriteLine(GenericCaster2<string>(12345));
        Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");

        //will not compile -> 20.4 does not comply due to the type constraint "T : class"
        //Console.WriteLine(GenericCaster2<double>(20.4));
    }

    static T GenericCaster<T>(object value, T defaultValue = default(T))
    {
        T castedValue;
        try
        {
            castedValue = (T) Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }

    static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
    {
        T castedValue;
        try
        {
            castedValue = Convert.ChangeType(value, typeof(T)) as T;
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }
}

Fazit : GenericCaster2 funktioniert nicht mit Strukturtypen. GenericCaster wird.

Veverke
quelle
1

Wenn Sie die Office-PIAs verwenden, die auf .NET Framework 4.X abzielen, sollten Sie das Schlüsselwort as verwenden , da es sonst nicht kompiliert wird.

Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;

Das Casting ist jedoch in Ordnung, wenn Sie auf .NET 2.0 abzielen:

Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);

Beim Targeting von .NET 4.X sind die Fehler:

Fehler CS0656: Fehlender Compiler erforderlich Mitglied 'Microsoft.CSharp.RuntimeBinder.Binder.Convert'

Fehler CS0656: Fehlender Compiler erforderlich Mitglied 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'

Olivier MATROT
quelle
0

Das asSchlüsselwort funktioniert genauso wie eine explizite Umwandlung zwischen kompatiblen Referenztypen, mit dem Hauptunterschied, dass es keine Ausnahme auslöst, wenn die Konvertierung fehlschlägt. Vielmehr ergibt es einen Nullwert in der Zielvariablen. Da Ausnahmen in Bezug auf die Leistung sehr teuer sind, wird sie als viel bessere Gießmethode angesehen.

Cerebrus
quelle
Nicht dasselbe, da einer CastClass und der andere IsInst im IL-Code aufruft.
Jenix
0

Was Sie wählen, hängt stark davon ab, was erforderlich ist. Ich bevorzuge explizites Casting

IMyInterface = (IMyInterface)someobj;

denn wenn das Objekt vom Typ IMyInterface sein sollte und es nicht ist, ist es definitiv ein Problem. Es ist besser, Fehler so früh wie möglich zu erhalten, da der genaue Fehler behoben wird, anstatt seine Nebenwirkung zu beheben.

Wenn Sie sich jedoch mit Methoden befassen, die objectals Parameter akzeptiert werden, müssen Sie den genauen Typ überprüfen, bevor Sie Code ausführen. In einem solchen Fall aswäre nützlich, damit Sie vermeiden können InvalidCastException.

Oleg
quelle
0

Es hängt davon ab, ob Sie nach der Verwendung von "as" nach null suchen möchten oder ob Sie es vorziehen, dass Ihre App eine Ausnahme auslöst.

Meine Faustregel lautet, wenn ich immer erwarte, dass die Variable von dem Typ ist, den ich zum Zeitpunkt der Verwendung einer Besetzung erwarte. Wenn es möglich ist, dass die Variable nicht in das umgewandelt wird, was ich möchte, und ich bereit bin, Nullen bei der Verwendung von as zu verarbeiten, verwende ich as.

Darryl Braaten
quelle
0

Das Problem des OP ist auf eine bestimmte Castingsituation beschränkt. Der Titel deckt viel mehr Situationen ab.
Hier ist eine Übersicht aller relevanten Castingsituationen, an die ich derzeit denken kann:

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}
Tobias Knauss
quelle