Casting vs menggunakan kata kunci 'sebagai' di CLR

387

Ketika pemrograman antarmuka, saya telah menemukan saya melakukan banyak casting atau konversi tipe objek.

Apakah ada perbedaan antara kedua metode konversi ini? Jika demikian, apakah ada perbedaan biaya atau bagaimana hal ini memengaruhi program saya?

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

Juga, apa yang "secara umum" metode yang disukai?

Frank V
sumber
Bisakah Anda menambahkan contoh kecil mengapa Anda menggunakan gips di tempat pertama untuk pertanyaan, atau mungkin memulai yang baru? Saya agak tertarik pada mengapa Anda akan membutuhkan para pemain untuk pengujian unit saja. Saya pikir itu di luar ruang lingkup pertanyaan ini.
Erik van Brakel
2
Saya mungkin dapat mengubah unit test saya untuk mencegah kebutuhan ini. Pada dasarnya bermuara pada kenyataan bahwa saya memiliki properti pada objek konkret saya yang tidak ada di antarmuka. Saya perlu mengatur properti itu tetapi kehidupan nyata properti itu akan diatur dengan cara lain. Apakah itu menjawab pertanyaan Anda?
Frank V
Seperti yang ditunjukkan oleh Patrik Hägne di bawah ini, ADA perbedaannya.
Neil

Jawaban:

519

Jawaban di bawah garis ditulis pada tahun 2008.

C # 7 memperkenalkan pencocokan pola, yang sebagian besar telah menggantikan asoperator, seperti sekarang Anda dapat menulis:

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

Perhatikan bahwa ttmasih dalam cakupan setelah ini, tetapi tidak ditetapkan secara pasti. (Hal ini jelas ditetapkan dalam iftubuh.) Itu sedikit mengganggu dalam beberapa kasus, jadi jika Anda benar-benar peduli tentang memperkenalkan jumlah terkecil variabel mungkin dalam setiap lingkup, Anda mungkin masih ingin menggunakan isdiikuti oleh gips.


Saya tidak berpikir satu pun dari jawaban sejauh ini (pada saat memulai jawaban ini!) Benar-benar menjelaskan di mana layak menggunakan yang mana.

  • Jangan lakukan ini:

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

    Ini tidak hanya memeriksa dua kali, tetapi mungkin memeriksa hal-hal yang berbeda, jika randomObjectmerupakan bidang daripada variabel lokal. Dimungkinkan untuk "jika" untuk lulus tetapi kemudian para pemain gagal, jika utas lain mengubah nilai di randomObjectantara keduanya.

  • Jika randomObjectbenar - benar harus menjadi contoh TargetType, yaitu jika tidak, itu berarti ada bug, maka casting adalah solusi yang tepat. Itu melempar pengecualian segera, yang berarti bahwa tidak ada lagi pekerjaan yang dilakukan di bawah asumsi yang salah, dan pengecualian dengan benar menunjukkan jenis bug.

    // 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;
  • Jika randomObject mungkin merupakan instance dari TargetTypedan TargetTypemerupakan tipe referensi, maka gunakan kode seperti ini:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
  • Jika randomObject mungkin merupakan instance dari TargetTypedan TargetTypemerupakan tipe nilai, maka kita tidak dapat menggunakannya asdengan TargetTypesendirinya, tetapi kita dapat menggunakan tipe nullable:

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

    (Catatan: saat ini ini sebenarnya lebih lambat daripada + pemain . Saya pikir ini lebih elegan dan konsisten, tapi begitulah.)

  • Jika Anda benar-benar tidak membutuhkan nilai yang dikonversi, tetapi Anda hanya perlu tahu apakah ini merupakan instance dari TargetType, maka isoperator adalah teman Anda. Dalam hal ini, tidak masalah apakah TargetType adalah tipe referensi atau tipe nilai.

  • Mungkin ada kasus-kasus lain yang melibatkan obat generik di mana isberguna (karena Anda mungkin tidak tahu apakah T adalah tipe referensi atau tidak, jadi Anda tidak dapat menggunakannya sebagai) tetapi mereka relatif tidak jelas.

  • Saya hampir pasti menggunakan iscase type value sebelumnya, tidak pernah berpikir untuk menggunakan tipe nullable dan asbersama - sama :)


EDIT: Perhatikan bahwa tidak ada yang di atas berbicara tentang kinerja, selain dari case type value, di mana saya telah mencatat bahwa unboxing ke tipe nilai nullable sebenarnya lebih lambat - tetapi konsisten.

Sesuai jawaban naasking, is-and-cast atau is-and-as sama cepat dan as-null-check dengan JIT modern, seperti yang ditunjukkan oleh kode di bawah ini:

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

Di laptop saya, semua ini dijalankan dalam waktu sekitar 60 ms. Dua hal yang perlu diperhatikan:

  • Tidak ada perbedaan yang signifikan di antara mereka. (Bahkan, ada situasi di mana as-plus-nol-cek pasti adalah lebih lambat Kode di atas benar-benar membuat jenis pemeriksaan yang mudah karena untuk kelas disegel;. Jika Anda memeriksa sedang untuk antarmuka, tips keseimbangan sedikit mendukung as-plus-null-check.)
  • Mereka semua sangat cepat. Ini tidak akan menjadi hambatan dalam kode Anda kecuali Anda benar-benar tidak akan melakukan apa pun dengan nilai-nilai sesudahnya.

Jadi jangan khawatir tentang kinerjanya. Mari kita khawatirkan tentang kebenaran dan konsistensi.

Saya berpendapat bahwa is-and-cast (atau is-and-as) sama-sama tidak aman ketika berhadapan dengan variabel, karena jenis nilai yang dirujuknya dapat berubah karena ada utas lain antara pengujian dan para pemeran. Itu akan menjadi situasi yang sangat langka - tetapi saya lebih suka memiliki konvensi yang dapat saya gunakan secara konsisten.

Saya juga berpendapat bahwa as-then-null-check memberikan pemisahan keprihatinan yang lebih baik. Kami memiliki satu pernyataan yang mencoba konversi, dan kemudian satu pernyataan yang menggunakan hasilnya. Is-and-cast atau is-and-as melakukan tes dan kemudian upaya lain untuk mengkonversi nilai.

Dengan kata lain, adakah yang pernah menulis:

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

Itulah yang dilakukan dan dilakukan - meskipun jelas dengan cara yang lebih murah.

Jon Skeet
sumber
7
Berikut adalah biaya dari / sebagai / casting dalam hal IL: atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
plinth
3
Dalam hal, jika targetObject mungkin tipe target, mengapa penggunaan "is" dan kombinasi cor dianggap praktik yang buruk? Maksud saya, ini menghasilkan kode yang lebih lambat, tetapi dalam hal ini niat lebih jelas daripada AS, seperti "Lakukan sesuatu jika targetObject adalah targetType", alih-alih "Lakukan sesuatu jika targetObject adalah nol", Selain itu klausa AS akan membuat variabel yang tidak perlu di luar lingkup IF.
Valera Kolupaev
2
@Valera: Poin bagus, walaupun saya menyarankan bahwa tes as / null cukup idiomatis sehingga maksudnya harus jelas bagi hampir semua pengembang C #. Saya tidak suka duplikasi yang terlibat dalam + casting, secara pribadi. Saya benar-benar ingin semacam konstruksi "as-if" yang melakukan kedua tindakan dalam satu. Mereka sering pergi bersama ...
Jon Skeet
2
@Jon Skeet: maaf atas keterlambatan saya.Is And Cast: 2135, Is And As: 2145, As And null check: 1961, spesifikasi: OS: Windows Seven, CPU: i5-520M, 4GB DDR3 1033 ram, benchmark pada array dari 128.000.000 item.
Behrooz
2
Dengan C # 7 Anda dapat melakukan: if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}atau menggunakan switch/ case melihat dokumen
WerWet
72

"as" akan mengembalikan NULL jika tidak memungkinkan untuk melakukan cast.

casting sebelumnya akan memunculkan eksepsi.

Untuk kinerja, meningkatkan pengecualian biasanya lebih mahal dalam waktu.

Patrick Desjardins
sumber
3
Peningkatan eksepsi lebih mahal, tetapi jika Anda tahu bahwa objek dapat dilemparkan dengan benar, karena memerlukan lebih banyak waktu karena pemeriksaan keamanan (lihat respons Anton). Namun, biaya pemeriksaan keamanan, saya percaya, cukup kecil.
17
Biaya yang berpotensi menimbulkan pengecualian adalah faktor yang perlu dipertimbangkan, tetapi seringkali merupakan desain yang tepat.
Jeffrey L Whitledge
@panesofglass - Untuk jenis referensi, kompatibilitas konversi akan selalu diperiksa pada saat run-time untuk kedua sebagai dan dilemparkan, sehingga faktor itu tidak akan membedakan antara dua opsi. (Jika ini tidak begitu, maka pemain tidak bisa meningkatkan pengecualian.)
Jeffrey L Whitledge
4
@ Frank - Jika Anda diharuskan untuk menggunakan koleksi pra-generik, misalnya, dan metode di API Anda memerlukan daftar Karyawan, dan beberapa pelawak malah melewati daftar Produk, maka pengecualian pemain yang tidak valid mungkin sesuai untuk memberi sinyal pelanggaran persyaratan antarmuka.
Jeffrey L Whitledge
27

Inilah jawaban lain, dengan beberapa perbandingan IL. Pertimbangkan kelasnya:

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

Sekarang lihat IL yang dihasilkan setiap metode. Bahkan jika kode op tidak ada artinya bagi Anda, Anda dapat melihat satu perbedaan besar - isinst dipanggil diikuti oleh castclass dalam metode DirectCast. Jadi dua panggilan bukan satu pada dasarnya.

.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

Kata kunci pertama versus castclass

Posting blog ini memiliki perbandingan yang layak antara dua cara melakukannya. Ringkasannya adalah:

  • Dalam perbandingan langsung, isinst lebih cepat dari castclass (walaupun hanya sedikit)
  • Ketika harus melakukan pemeriksaan untuk memastikan konversi berhasil, isinst secara signifikan lebih cepat daripada castclass
  • Kombinasi isinst dan castclass tidak boleh digunakan karena ini jauh lebih lambat daripada konversi "aman" tercepat (lebih dari 12% lebih lambat)

Saya pribadi selalu menggunakan As, karena mudah dibaca dan direkomendasikan oleh tim pengembangan .NET (atau tetap saja Jeffrey Richter)

Chris S
sumber
Saya sedang mencari penjelasan yang jelas untuk casting vs sebagai, jawaban ini membuatnya lebih jelas karena melibatkan penjelasan langkah-langkah bahasa perantara umum. Terima kasih!
Morse
18

Salah satu perbedaan yang lebih kentara antara keduanya adalah bahwa kata kunci "sebagai" tidak dapat digunakan untuk casting ketika operator pemain terlibat:

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

Ini tidak akan dikompilasi (meskipun saya pikir itu dalam versi sebelumnya) pada baris terakhir karena kata kunci "sebagai" tidak memperhitungkan operator yang terlibat. Garis string cast = (string)f;bekerja dengan baik.

Patrik Hägne
sumber
12

sebagai tidak pernah melempar pengecualian jika ia tidak dapat melakukan konversi yang menghasilkan null sebagai gantinya ( seperti yang beroperasi pada tipe referensi saja). Jadi menggunakan seperti pada dasarnya setara dengan

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

Pemain C-style, di sisi lain, melemparkan pengecualian ketika tidak ada konversi yang dimungkinkan.

Anton Gogolev
sumber
4
Setara, ya, tapi tidak sama. Ini menghasilkan lebih banyak kode daripada sebagai.
alas
10

Tidak benar-benar jawaban untuk pertanyaan Anda, tetapi apa yang saya pikir merupakan poin terkait yang penting.

Jika Anda memprogram ke antarmuka, Anda tidak perlu melakukan cast. Semoga gips ini sangat langka. Jika tidak, Anda mungkin perlu memikirkan kembali beberapa antarmuka Anda.

kodok
sumber
Pengecoran, sejauh ini, sebagian besar diperlukan untuk Pengujian Unit saya, tetapi terima kasih telah membawanya. Saya akan mengingatnya saat saya mengerjakan ini.
Frank V
Setuju dengan kodok, saya juga ingin tahu mengapa aspek pengujian unit relevan dengan casting untuk Anda @ Frank V. Di mana ada kebutuhan untuk casting, sering kali ada kebutuhan untuk mendesain ulang atau refactor karena menunjukkan bahwa Anda sedang mencoba untuk memperbaiki masalah yang berbeda di mana mereka harus dikelola secara berbeda.
Senator
@TheSenator Pertanyaan ini sudah lebih dari 3 tahun jadi saya tidak begitu ingat. Tapi saya mungkin secara agresif menggunakan antarmuka bahkan ketika pengujian unit. Mungkin karena saya menggunakan pola pabrik dan tidak memiliki akses ke konstruktor publik pada objek target untuk diuji.
Frank V
9

Harap abaikan saran Jon Skeet, ulang: hindari pola uji-dan-cetak, yaitu .:

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

Gagasan bahwa ini lebih mahal dari gips dan tes nol adalah MITOS :

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

Ini adalah mikro-optimasi yang tidak berfungsi. Saya menjalankan beberapa tes nyata , dan test-and-cast sebenarnya lebih cepat daripada cast-and-null-comparison, dan lebih aman juga karena Anda tidak memiliki kemungkinan memiliki referensi nol dalam lingkup di luar jika seharusnya para pemain gagal.

Jika Anda menginginkan alasan mengapa uji coba lebih cepat, atau setidaknya tidak lebih lambat, ada alasan sederhana dan kompleks.

Sederhana: bahkan kompiler yang naif akan menyatukan dua operasi yang sama, seperti test-and-cast, menjadi satu test dan branch. cast-and-null-test dapat memaksa dua tes dan satu cabang, satu untuk tes tipe dan konversi menjadi nol pada kegagalan, satu untuk pemeriksaan nol itu sendiri. Paling tidak, mereka berdua akan mengoptimalkan untuk satu tes dan cabang, sehingga uji-dan-gips tidak akan lebih lambat atau lebih cepat dari uji-dan-nol-uji.

Kompleks: mengapa uji-dan tuang lebih cepat: uji tuang-dan-nol memasukkan variabel lain ke dalam lingkup luar yang harus dilacak oleh kompiler untuk liveness, dan mungkin tidak dapat mengoptimalkan variabel itu tergantung pada seberapa rumit kontrol Anda- mengalir. Sebaliknya, test-and-cast memperkenalkan variabel baru hanya dalam lingkup terbatas sehingga kompiler tahu bahwa variabel mati setelah lingkup keluar, dan dengan demikian dapat mengoptimalkan alokasi register dengan lebih baik.

Jadi tolong, HARAP biarkan ini "cast-and-null-test lebih baik daripada saran test-and-cast" DIE. SILAHKAN. test-and-cast lebih aman dan lebih cepat.

naasking
sumber
7
@naasking: Jika Anda menguji dua kali (sesuai cuplikan pertama Anda), ada kemungkinan jenis akan berubah di antara dua tes, jika itu bidang atau refparameter. Aman untuk variabel lokal, tetapi tidak untuk bidang. Saya akan tertarik untuk menjalankan benchmark Anda, tetapi kode yang Anda berikan di posting blog Anda tidak lengkap. Saya setuju dengan tidak mengoptimalkan mikro, tetapi saya tidak berpikir menggunakan nilai dua kali lebih mudah dibaca atau elegan daripada menggunakan "sebagai" dan tes nullity. (Saya pasti akan menggunakan gips lurus alih-alih "sebagai" setelah, adalah.)
Jon Skeet
5
Saya juga tidak mengerti mengapa ini lebih aman. Saya sudah menunjukkan mengapa itu kurang aman. Tentu, Anda berakhir dengan variabel dalam cakupan yang mungkin nol, tetapi kecuali Anda mulai menggunakannya di luar lingkup blok "jika" berikutnya, Anda baik-baik saja. Masalah keamanan yang saya ajukan (seputar bidang yang mengubah nilainya) adalah masalah asli dengan kode yang ditampilkan - masalah keamanan Anda mengharuskan pengembang menjadi longgar dalam kode lain.
Jon Skeet
1
+1 untuk menunjukkan bahwa / cast atau sebagai / cast tidak lebih lambat dalam kenyataannya, ingatlah. Setelah menjalankan tes lengkap sendiri, saya dapat mengonfirmasi bahwa tidak ada bedanya sejauh yang saya lihat - dan terus terang Anda dapat menjalankan sejumlah gips yang membingungkan dalam waktu yang sangat kecil. Akan memperbarui jawaban saya dengan kode lengkap.
Jon Skeet
1
Memang, jika pengikatannya bukan lokal, ada kemungkinan bug TOCTTOU (waktu-periksa-ke-waktu-penggunaan), jadi ada baiknya di sana. Untuk alasan mengapa ini lebih aman, saya bekerja dengan banyak pengembang junior yang suka menggunakan kembali penduduk setempat karena suatu alasan. cast-and-null dengan demikian sangat berbahaya dalam pengalaman saya, dan saya tidak pernah mengalami situasi TOCTTOU karena saya tidak mendesain kode saya seperti itu. Adapun kecepatan uji runtime, bahkan lebih cepat daripada pengiriman virtual [1]! Re: kode, saya akan melihat apakah saya dapat menemukan sumber untuk uji coba. [1] higherlogics.blogspot.com/2008/10/…
naasking
1
@naasking: Saya tidak pernah mengalami masalah penggunaan kembali lokal - tetapi saya akan mengatakan lebih mudah untuk menemukan kode review daripada bug TOCTTOU yang lebih halus. Ini juga layak menunjukkan bahwa saya baru saja menjalankan kembali benchmark saya sendiri memeriksa untuk antarmuka bukan kelas disegel, dan itu tips kinerja yang mendukung sebagai-maka-null-cek ... tapi seperti yang saya katakan, kinerja tidak Itu sebabnya saya memilih pendekatan tertentu di sini.
Jon Skeet
4

Jika para pemain gagal, kata kunci 'as' tidak memberikan pengecualian; itu menetapkan variabel ke null (atau ke nilai default untuk tipe nilai) sebagai gantinya.

TheSmurf
sumber
3
Tidak ada nilai default untuk tipe nilai. Karena tidak dapat digunakan untuk tipe nilai casting.
Patrik Hägne
2
Kata kunci 'as' sebenarnya tidak berfungsi pada tipe nilai, jadi kata kunci itu selalu null.
Erik van Brakel
4

Ini bukan jawaban untuk pertanyaan tetapi komentar untuk contoh kode pertanyaan:

Biasanya Anda tidak harus melemparkan Obyek dari mis. IMyInterface ke MyClass. Yang hebat tentang antarmuka adalah bahwa jika Anda mengambil objek sebagai input yang mengimplementasikan antarmuka, maka Anda tidak perlu peduli objek apa yang Anda peroleh.

Jika Anda menggunakan IMyInterface ke MyClass, maka Anda sudah menganggap bahwa Anda mendapatkan objek bertipe MyClass dan tidak masuk akal untuk menggunakan IMyInterface, karena jika Anda memberi makan kode Anda dengan kelas lain yang mengimplementasikan IMyInterface, itu akan merusak kode Anda ...

Sekarang, saran saya: jika antarmuka Anda dirancang dengan baik, Anda dapat menghindari banyak typecasting.

f3lix
sumber
3

The asOperator hanya dapat digunakan pada jenis referensi, tidak dapat kelebihan beban, dan akan kembali nulljika operasi gagal. Tidak akan pernah ada pengecualian.

Casting dapat digunakan pada semua jenis yang kompatibel, dapat kelebihan beban, dan itu akan menimbulkan pengecualian jika operasi gagal.

Pilihan yang akan digunakan tergantung pada keadaan. Terutama, ini masalah apakah Anda ingin melemparkan pengecualian pada konversi yang gagal.

Jeffrey L Whitledge
sumber
1
'as' juga dapat digunakan pada tipe nilai yang dapat dibatalkan, yang menyediakan pola yang menarik. Lihat jawaban saya untuk kode.
Jon Skeet
1

Jawaban saya hanya tentang kecepatan jika kami tidak memeriksa jenis dan kami tidak memeriksa nol setelah casting. Saya menambahkan dua tes tambahan ke kode Jon Skeet:

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

Hasil:

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

Jangan mencoba fokus pada kecepatan (seperti yang saya lakukan) karena semua ini sangat cepat.

CoperNick
sumber
Demikian pula, dalam pengujian saya, saya menemukan bahwa askonversi (tanpa pengecekan kesalahan) berjalan sekitar 1-3% lebih cepat daripada casting (sekitar 540ms versus 550ms pada 100 juta iterasi). Tidak akan membuat atau merusak aplikasi Anda.
palswim
1

Selain semua yang sudah diungkapkan di sini, saya hanya menemukan perbedaan praktis yang menurut saya perlu diperhatikan, antara casting eksplisit

var x = (T) ...

dibandingkan menggunakan asoperator.

Berikut ini contohnya:

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

Intinya: GenericCaster2 tidak akan berfungsi dengan tipe struct. GenericCaster akan.

Veverke
sumber
1

Jika Anda menggunakan Office PIA yang menargetkan .NET Framework 4.X Anda harus menggunakan kata kunci as , jika tidak, ia tidak akan dikompilasi.

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;

Casting tidak apa-apa saat menargetkan .NET 2.0:

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

Saat menargetkan .NET 4.X kesalahannya adalah:

kesalahan CS0656: Kompilator yang hilang diperlukan anggota 'Microsoft.CSharp.RuntimeBinder.Binder.Convert'

kesalahan CS0656: Kompilator yang hilang diperlukan anggota 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'

Olivier MATROT
sumber
0

Kata askunci bekerja sama dengan cast eksplisit antara tipe referensi yang kompatibel dengan perbedaan utama yang tidak menimbulkan pengecualian jika konversi gagal. Sebaliknya, ini menghasilkan nilai nol dalam variabel target. Karena Pengecualian sangat mahal dalam hal kinerja, itu dianggap sebagai metode casting yang jauh lebih baik.

Cerebrus
sumber
Tidak sama, seperti satu panggilan CastClass dan panggilan lainnya IsInst dalam kode IL.
Jenix
0

Apa yang Anda pilih sangat tergantung pada apa yang diperlukan. Saya lebih suka casting eksplisit

IMyInterface = (IMyInterface)someobj;

karena jika objek harus dengan tipe IMyInterface dan tidak - itu pasti masalah. Lebih baik untuk mendapatkan kesalahan sedini mungkin karena kesalahan yang tepat akan diperbaiki daripada memperbaiki efek sampingnya.

Tetapi jika Anda berurusan dengan metode yang menerima objectsebagai parameter maka Anda perlu memeriksa jenisnya yang tepat sebelum mengeksekusi kode apa pun. Dalam hal demikian asakan bermanfaat agar Anda bisa menghindar InvalidCastException.

Oleg
sumber
0

Tergantung, apakah Anda ingin memeriksa nol setelah menggunakan "sebagai" atau apakah Anda lebih suka aplikasi Anda untuk melemparkan pengecualian?

Aturan praktis saya adalah jika saya selalu berharap variabel dari tipe yang saya harapkan pada saat saya ingin saya menggunakan gips. Jika ada kemungkinan variabel tidak akan melakukan apa yang saya inginkan dan saya siap menangani nulls dari menggunakan as, saya akan menggunakan as.

Darryl Braaten
sumber
0

Masalah OP terbatas pada situasi casting tertentu. Judul ini mencakup lebih banyak situasi.
Inilah gambaran umum dari semua situasi casting yang relevan yang saat ini dapat saya pikirkan:

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
sumber