Apakah ada alternatif yang lebih baik dari ini untuk 'mengaktifkan tipe'?


331

Menganggap C # tidak bisa switchdi Tipe (yang saya kumpulkan tidak ditambahkan sebagai kasus khusus karena ishubungan berarti bahwa lebih dari satu perbedaan casemungkin berlaku), apakah ada cara yang lebih baik untuk mensimulasikan beralih pada tipe selain ini?

void Foo(object o)
{
    if (o is A)
    {
        ((A)o).Hop();
    }
    else if (o is B)
    {
        ((B)o).Skip();
    }
    else
    {
        throw new ArgumentException("Unexpected type: " + o.GetType());
    }
}

18
Karena penasaran, mengapa Anda tidak menggunakan polimorfisme saja?

18
@jeyoung menyegel kelas, dan itu tidak layak untuk situasi ad-hoc
xyz



2
@jeyoung: Satu situasi umum di mana polimorfisme tidak dapat digunakan adalah ketika tipe yang sedang dialihkan tidak boleh tahu kode yang berisi switchpernyataan. Satu contoh: Perakitan A berisi sekumpulan objek data (yang tidak akan berubah, ditentukan dalam dokumen spesifikasi atau semacamnya). Rakitan B , C , dan D masing-masing referensi A dan memberikan konversi untuk berbagai objek data dari A (misalnya serialisasi / deserialisasi ke beberapa format tertentu). Anda harus mencerminkan seluruh hierarki kelas di B , C , dan D , dan menggunakan pabrik, atau Anda harus ...
ATAU Mapper

Jawaban:


276

Mengaktifkan jenis pasti kurang dalam C # ( UPDATE: di C # 7 / VS 2017 diaktifkan jenis didukung - lihat jawaban Zachary Yates di bawah ). Untuk melakukan ini tanpa pernyataan if / else if / else yang besar, Anda harus bekerja dengan struktur yang berbeda. Saya menulis posting blog sementara merinci bagaimana membangun struktur TypeSwitch.

https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types

Versi singkat: TypeSwitch dirancang untuk mencegah pengecoran redundan dan memberikan sintaksis yang mirip dengan pernyataan sakelar / kasus normal. Misalnya, di sini adalah TypeSwitch beraksi pada acara formulir Windows standar

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));

Kode untuk TypeSwitch sebenarnya cukup kecil dan dapat dengan mudah dimasukkan ke dalam proyek Anda.

static class TypeSwitch {
    public class CaseInfo {
        public bool IsDefault { get; set; }
        public Type Target { get; set; }
        public Action<object> Action { get; set; }
    }

    public static void Do(object source, params CaseInfo[] cases) {
        var type = source.GetType();
        foreach (var entry in cases) {
            if (entry.IsDefault || entry.Target.IsAssignableFrom(type)) {
                entry.Action(source);
                break;
            }
        }
    }

    public static CaseInfo Case<T>(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            Target = typeof(T)
        };
    }

    public static CaseInfo Case<T>(Action<T> action) {
        return new CaseInfo() {
            Action = (x) => action((T)x),
            Target = typeof(T)
        };
    }

    public static CaseInfo Default(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            IsDefault = true
        };
    }
}

26
"type == entry.Target" juga dapat diubah menjadi "entry.Target.IsAssignableFrom (type)" untuk memasukkan tipe yang kompatibel (mis., subclass) ke dalam akun.
Mark Cidade

Mengubah kode untuk menggunakan "entry.Target.IsAssignableFrom (type)" sehingga subkelas didukung.
Matt Howells

3
Satu hal yang mungkin perlu diperhatikan adalah bahwa (dari apa yang saya pahami) diperlukan untuk menentukan tindakan 'default' terakhir untuk memastikan semua kasus lain dicentang. Saya percaya ini bukan persyaratan dalam saklar standar - bukan berarti saya pernah melihat orang mencoba menanam 'default' di tempat lain selain bagian bawah. Beberapa opsi gagal yang aman untuk ini bisa dengan memesan array untuk memastikan default adalah terakhir (sedikit boros) atau pop default dalam variabel yang akan diproses setelah foreach(yang hanya akan terjadi jika kecocokan tidak ditemukan)
musefan

Bagaimana jika pengirimnya nol? GetType akan memberikan pengecualian
Jon

Dua saran: Tangani sumber nol dengan memanggil default atau melempar pengecualian dan singkirkan boolean CaseInfodengan hanya memeriksa terhadap nilai jenis (jika nolnya adalah default).
Felix K.

291

Dengan C # 7 , yang dikirimkan bersama Visual Studio 2017 (Rilis 15. *), Anda dapat menggunakan Jenis dalam casepernyataan (pencocokan pola):

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

Dengan C # 6, Anda dapat menggunakan pernyataan switch dengan operator nameof () (terima kasih @ Joey Adams):

switch(o.GetType().Name) {
    case nameof(AType):
        break;
    case nameof(BType):
        break;
}

Dengan C # 5 dan sebelumnya, Anda bisa menggunakan pernyataan peralihan, tetapi Anda harus menggunakan string ajaib yang berisi nama jenis ... yang tidak terlalu ramah untuk refactor (terima kasih @nukefusion)

switch(o.GetType().Name) {
  case "AType":
    break;
}

1
apakah ini bekerja dengan jenis huruf (string) .Nama: ... atau harus dengan Valuetype?
Tomer W

3
Kebingungan dapat memecahkannya
Konrad Morawski

6
@nukefusion: Yaitu, kecuali Anda menggunakan nameof()operator baru yang mengkilap .
Joey Adams

21
Saya tidak suka jawaban ini karena nameof (NamespaceA.ClassC) == nameof (NamespaceB.ClassC) benar.
ischas

7
(c # 7) Anda juga dapat menggunakan garis bawah jika Anda tidak memerlukan akses ke objek:case UnauthorizedException _:
Assaf S.

101

Salah satu opsi adalah memiliki kamus dari Typeke Action(atau delegasi lain). Cari tindakan berdasarkan tipe, dan kemudian jalankan. Saya telah menggunakan ini untuk pabrik sebelumnya.


31
Catatan kecil: bagus untuk pertandingan 1: 1, tetapi mungkin merepotkan dengan warisan dan / atau antarmuka - terutama karena pesanan tidak dijamin untuk disimpan dengan kamus. Tapi tetap saja, itu adalah cara saya melakukannya di beberapa tempat yang adil ;-p Jadi +1
Marc Gravell

@ Markc: Bagaimana warisan atau antarmuka akan pecah dalam paradigma ini? Dengan asumsi kunci adalah tipe, dan aksi adalah metode, maka pewarisan atau antarmuka harus benar-benar memaksa Right Thing (TM), sejauh yang saya tahu. Saya tentu mengerti masalah dengan beberapa tindakan dan kurangnya pemesanan.
Harper Shelby

2
Saya telah menggunakan teknik ini banyak di masa lalu, biasanya sebelum pindah ke Kontainer IoC
Chris Canal

4
Teknik ini memecah untuk warisan dan antarmuka karena Anda memerlukan korespondensi satu-ke-satu antara objek yang Anda periksa dan delegasi yang Anda panggil. Manakah dari beberapa antarmuka objek yang harus Anda coba temukan di kamus?
Robert Rossney

5
Jika Anda membuat kamus khusus untuk tujuan ini, Anda bisa membebani pengindeks untuk mengembalikan nilai tipe kunci, atau jika hilang maka superclassnya, jika itu hilang maka superclass itu, dll., Hingga tidak ada yang tersisa.
Erik Forbes

49

Dengan jawaban JaredPar di belakang kepala saya, saya menulis varian TypeSwitchkelasnya yang menggunakan inferensi tipe untuk sintaksis yang lebih bagus:

class A { string Name { get; } }
class B : A { string LongName { get; } }
class C : A { string FullName { get; } }
class X { public string ToString(IFormatProvider provider); }
class Y { public string GetIdentifier(); }

public string GetName(object value)
{
    string name = null;
    TypeSwitch.On(value)
        .Case((C x) => name = x.FullName)
        .Case((B x) => name = x.LongName)
        .Case((A x) => name = x.Name)
        .Case((X x) => name = x.ToString(CultureInfo.CurrentCulture))
        .Case((Y x) => name = x.GetIdentifier())
        .Default((x) => name = x.ToString());
    return name;
}

Perhatikan bahwa urutan Case()metode ini penting.


Dapatkan kode lengkap dan komentar untuk TypeSwitchkelas saya . Ini adalah versi singkat yang berfungsi:

public static class TypeSwitch
{
    public static Switch<TSource> On<TSource>(TSource value)
    {
        return new Switch<TSource>(value);
    }

    public sealed class Switch<TSource>
    {
        private readonly TSource value;
        private bool handled = false;

        internal Switch(TSource value)
        {
            this.value = value;
        }

        public Switch<TSource> Case<TTarget>(Action<TTarget> action)
            where TTarget : TSource
        {
            if (!this.handled && this.value is TTarget)
            {
                action((TTarget) this.value);
                this.handled = true;
            }
            return this;
        }

        public void Default(Action<TSource> action)
        {
            if (!this.handled)
                action(this.value);
        }
    }
}

Sepertinya solusi yang bagus dan ingin melihat apa lagi yang Anda katakan tentang itu tetapi blog sudah mati.
Wes Grant

1
Sial, kau benar. Hosting saya mengalami beberapa masalah sejak satu jam. Mereka sedang mengerjakannya. Posting di blog saya pada dasarnya sama dengan jawaban di sini, tetapi dengan tautan ke kode sumber lengkap.
Daniel AA Pelsmaeker

1
Cinta bagaimana ini mengurangi banyak tanda kurung ke saklar "fungsional" sederhana. Kerja bagus!
James White

2
Anda juga dapat menambahkan metode ekstensi untuk kasus awal: public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource. Ini memungkinkan Anda mengatakanvalue.Case((C x) ...
Joey Adams

1
@ JoeyAdams: Saya memasukkan saran terakhir Anda, bersama dengan beberapa perbaikan kecil. Namun, saya tetap menggunakan sintaks yang sama.
Daniel AA Pelsmaeker

14

Buat superclass (S) dan buat A dan B mewarisinya. Kemudian nyatakan sebuah metode abstrak pada S yang perlu diterapkan oleh setiap subclass.

Melakukan hal ini dengan metode "foo" juga dapat mengubah tanda tangannya menjadi Foo (S o), membuatnya aman, dan Anda tidak perlu membuang pengecualian jelek itu.


Bruno yang sebenarnya, tapi pertanyaannya tidak menyarankan itu. Anda bisa memasukkan itu dalam jawaban Anda melalui Pablo.
Dana the Sane

Dari pertanyaan saya pikir A dan B cukup umum sehingga mereka bisa menjadi A = String; B = Daftar <int> misalnya ...
bruno conde

13

Anda dapat menggunakan pencocokan pola dalam C # 7 atau lebih tinggi:

switch (foo.GetType())
{
    case var type when type == typeof(Player):
        break;
    case var type when type == typeof(Address):
        break;
    case var type when type == typeof(Department):
        break;
    case var type when type == typeof(ContactType):
        break;
    default:
        break;
}

Terima kasih untuk yang ini! Dapat digunakan untuk mendeteksi subkelas juga: if (this.TemplatedParent.GetType (). IsSubclassOf (typeof (RadGridView)) dapat diubah menjadi: switch (this.TemplatedParent.GetType ()) case var subRadGridView saat subRadGridView.s typeof (RadGridView)):
Flemming Bonde Kentved

Yang kamu lakukan itu salah. Lihat jawabannya oleh Serge Intern dan baca tentang prinsip substitusi Liskov
0xF

8

Anda harus benar-benar membebani metode Anda, bukan mencoba melakukan disambiguasi sendiri. Sebagian besar jawaban sejauh ini tidak memperhitungkan subclass masa depan, yang dapat menyebabkan masalah pemeliharaan yang sangat mengerikan di kemudian hari.


3
Resolusi kelebihan ditentukan secara statis sehingga tidak akan berfungsi sama sekali.
Neutrino

@Neutrino: tidak ada dalam pertanyaan yang menentukan bahwa jenisnya tidak diketahui pada waktu kompilasi. Dan jika ya, kelebihan beban masuk akal lebih dari opsi lain, mengingat contoh kode asli OP.
Peter Duniho

Saya pikir fakta bahwa dia mencoba menggunakan pernyataan 'jika' atau 'beralih' untuk menentukan tipe adalah indikasi yang cukup jelas bahwa tipe tersebut tidak diketahui pada waktu kompilasi.
Neutrino

@Neutrino, saya ingat Anda bahwa, seperti yang ditunjukkan Sergey Berezovskiy, ada kata kunci dinamis dalam C #, yang mewakili jenis yang harus diselesaikan secara dinamis (saat runtime, daripada waktu kompilasi).
Davide Cannizzo

8

Jika Anda menggunakan C # 4, Anda dapat menggunakan fungsionalitas dinamis baru untuk mencapai alternatif yang menarik. Saya tidak mengatakan ini lebih baik, bahkan sepertinya sangat lambat, tetapi memang memiliki keanggunan tertentu.

class Thing
{

  void Foo(A a)
  {
     a.Hop();
  }

  void Foo(B b)
  {
     b.Skip();
  }

}

Dan penggunaannya:

object aOrB = Get_AOrB();
Thing t = GetThing();
((dynamic)t).Foo(aorB);

Alasan ini berhasil adalah bahwa pemanggilan metode dinamis C # 4 memiliki kelebihannya diselesaikan pada saat runtime daripada waktu kompilasi. Saya menulis sedikit lebih banyak tentang ide ini baru-baru ini . Sekali lagi, saya hanya ingin menegaskan kembali bahwa ini mungkin berkinerja lebih buruk daripada semua saran lainnya, saya menawarkannya hanya sebagai rasa ingin tahu.


1
Saya memiliki ide yang sama hari ini. Ini kira-kira 3 kali lebih lambat daripada mengaktifkan nama tipe. Tentu saja lebih lambat relatif (untuk 60.000.000 panggilan, hanya 4 detik), dan kode ini jauh lebih mudah dibaca.
Daryl

8

Ya, terima kasih kepada C # 7 yang dapat dicapai. Begini caranya (menggunakan pola ekspresi ):

switch (o)
{
    case A a:
        a.Hop();
        break;
    case B b:
        b.Skip();
        break;
    case C _: 
        return new ArgumentException("Type C will be supported in the next version");
    default:
        return new ArgumentException("Unexpected type: " + o.GetType());
}


7

Untuk tipe bawaan, Anda dapat menggunakan enumerasi TypeCode. Harap perhatikan bahwa GetType () agak lambat, tetapi mungkin tidak relevan di sebagian besar situasi.

switch (Type.GetTypeCode(someObject.GetType()))
{
    case TypeCode.Boolean:
        break;
    case TypeCode.Byte:
        break;
    case TypeCode.Char:
        break;
}

Untuk tipe khusus, Anda dapat membuat enumerasi sendiri, dan antarmuka atau kelas dasar dengan properti atau metode abstrak ...

Implementasi kelas abstrak properti

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes FooType { get; }
}
public class FooFighter : Foo
{
    public override FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Implementasi metode abstrak kelas

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes GetFooType();
}
public class FooFighter : Foo
{
    public override FooTypes GetFooType() { return FooTypes.FooFighter; }
}

Implementasi antarmuka properti

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
    public FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Implementasi antarmuka metode

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes GetFooType();
}
public class FooFighter : IFooType
{
    public FooTypes GetFooType() { return FooTypes.FooFighter; }
}

Salah satu rekan kerja saya baru saja memberi tahu saya tentang ini: Ini memiliki keuntungan bahwa Anda dapat menggunakannya untuk semua jenis objek, bukan hanya yang Anda tetapkan. Ini memiliki kelemahan menjadi sedikit lebih besar dan lebih lambat.

Pertama-tama tentukan kelas statis seperti ini:

public static class TypeEnumerator
{
    public class TypeEnumeratorException : Exception
    {
        public Type unknownType { get; private set; }
        public TypeEnumeratorException(Type unknownType) : base()
        {
            this.unknownType = unknownType;
        }
    }
    public enum TypeEnumeratorTypes { _int, _string, _Foo, _TcpClient, };
    private static Dictionary<Type, TypeEnumeratorTypes> typeDict;
    static TypeEnumerator()
    {
        typeDict = new Dictionary<Type, TypeEnumeratorTypes>();
        typeDict[typeof(int)] = TypeEnumeratorTypes._int;
        typeDict[typeof(string)] = TypeEnumeratorTypes._string;
        typeDict[typeof(Foo)] = TypeEnumeratorTypes._Foo;
        typeDict[typeof(System.Net.Sockets.TcpClient)] = TypeEnumeratorTypes._TcpClient;
    }
    /// <summary>
    /// Throws NullReferenceException and TypeEnumeratorException</summary>
    /// <exception cref="System.NullReferenceException">NullReferenceException</exception>
    /// <exception cref="MyProject.TypeEnumerator.TypeEnumeratorException">TypeEnumeratorException</exception>
    public static TypeEnumeratorTypes EnumerateType(object theObject)
    {
        try
        {
            return typeDict[theObject.GetType()];
        }
        catch (KeyNotFoundException)
        {
            throw new TypeEnumeratorException(theObject.GetType());
        }
    }
}

Dan kemudian Anda dapat menggunakannya seperti ini:

switch (TypeEnumerator.EnumerateType(someObject))
{
    case TypeEnumerator.TypeEnumeratorTypes._int:
        break;
    case TypeEnumerator.TypeEnumeratorTypes._string:
        break;
}

Terima kasih telah menambahkan TypeCode () - varian untuk tipe primitif, karena bahkan varian C # 7.0 - tidak dapat digunakan pada varian itu (begitu juga nameof () jelas)
Ole Albers

6

Saya menyukai penggunaan pengetikan implisit Virtlink untuk membuat pergantian jauh lebih mudah dibaca, tetapi saya tidak suka bahwa permulaan tidak mungkin, dan kami sedang mengalokasikan. Mari kita naikkan perf sedikit.

public static class TypeSwitch
{
    public static void On<TV, T1>(TV value, Action<T1> action1)
        where T1 : TV
    {
        if (value is T1) action1((T1)value);
    }

    public static void On<TV, T1, T2>(TV value, Action<T1> action1, Action<T2> action2)
        where T1 : TV where T2 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
    }

    public static void On<TV, T1, T2, T3>(TV value, Action<T1> action1, Action<T2> action2, Action<T3> action3)
        where T1 : TV where T2 : TV where T3 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
        else if (value is T3) action3((T3)value);
    }

    // ... etc.
}

Nah, itu membuat jari saya sakit. Mari kita lakukan di T4:

<#@ template debug="false" hostSpecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ import namespace="System.Linq" #> 
<#@ import namespace="System.IO" #> 
<#
    string GenWarning = "// THIS FILE IS GENERATED FROM " + Path.GetFileName(Host.TemplateFile) + " - ANY HAND EDITS WILL BE LOST!";
    const int MaxCases = 15;
#>
<#=GenWarning#>

using System;

public static class TypeSwitch
{
<# for(int icase = 1; icase <= MaxCases; ++icase) {
    var types = string.Join(", ", Enumerable.Range(1, icase).Select(i => "T" + i));
    var actions = string.Join(", ", Enumerable.Range(1, icase).Select(i => string.Format("Action<T{0}> action{0}", i)));
    var wheres = string.Join(" ", Enumerable.Range(1, icase).Select(i => string.Format("where T{0} : TV", i)));
#>
    <#=GenWarning#>

    public static void On<TV, <#=types#>>(TV value, <#=actions#>)
        <#=wheres#>
    {
        if (value is T1) action1((T1)value);
<# for(int i = 2; i <= icase; ++i) { #>
        else if (value is T<#=i#>) action<#=i#>((T<#=i#>)value);
<#}#>
    }

<#}#>
    <#=GenWarning#>
}

Menyesuaikan sedikit contoh Virtlink:

TypeSwitch.On(operand,
    (C x) => name = x.FullName,
    (B x) => name = x.LongName,
    (A x) => name = x.Name,
    (X x) => name = x.ToString(CultureInfo.CurrentCulture),
    (Y x) => name = x.GetIdentifier(),
    (object x) => name = x.ToString());

Dapat dibaca dan cepat. Sekarang, ketika semua orang terus menunjukkan jawaban mereka, dan mengingat sifat pertanyaan ini, urutan penting dalam jenis pencocokan. Karena itu:

  • Taruh jenis daun dulu, jenis pangkal nanti.
  • Untuk tipe sejawat, lebih dulu cocokkan yang paling cocok untuk memaksimalkan kinerja.
  • Ini menyiratkan bahwa tidak perlu untuk kasus default khusus. Sebagai gantinya, cukup gunakan tipe paling dasar di lambda, dan letakkan yang terakhir.

5

Mengingat warisan memfasilitasi suatu objek untuk dikenali sebagai lebih dari satu jenis, saya pikir peralihan dapat menyebabkan ambiguitas buruk. Sebagai contoh:

Kasus 1

{
  string s = "a";
  if (s is string) Print("Foo");
  else if (s is object) Print("Bar");
}

Kasus 2

{
  string s = "a";
  if (s is object) Print("Foo");
  else if (s is string) Print("Bar");
}

Karena s adalah string dan objek. Saya pikir ketika Anda menulis switch(foo)Anda berharap foo mencocokkan satu dan hanya satu dari casepernyataan tersebut. Dengan sakelar jenis, urutan di mana Anda menulis pernyataan kasus Anda mungkin dapat mengubah hasil seluruh pernyataan sakelar. Saya pikir itu salah.

Anda bisa memikirkan kompiler-periksa pada jenis pernyataan "typeswitch", memeriksa bahwa jenis yang disebutkan tidak mewarisi satu sama lain. Itu tidak ada.

foo is Ttidak sama dengan foo.GetType() == typeof(T)!!



4

Cara lain adalah dengan mendefinisikan antarmuka IThing dan kemudian mengimplementasikannya di kedua kelas di sini snipet:

public interface IThing
{
    void Move();
}

public class ThingA : IThing
{
    public void Move()
    {
        Hop();
    }

    public void Hop(){  
        //Implementation of Hop 
    }

}

public class ThingA : IThing
{
    public void Move()
    {
        Skip();
    }

    public void Skip(){ 
        //Implementation of Skip    
    }

}

public class Foo
{
    static void Main(String[] args)
    {

    }

    private void Foo(IThing a)
    {
        a.Move();
    }
}

4

Sesuai spesifikasi C # 7.0, Anda dapat mendeklarasikan variabel lokal yang dicakup dalam casea switch:

object a = "Hello world";
switch (a)
{
    case string myString:
        // The variable 'a' is a string!
        break;
    case int myInt:
        // The variable 'a' is an int!
        break;
    case Foo myFoo:
        // The variable 'a' is of type Foo!
        break;
}

Ini adalah cara terbaik untuk melakukan hal seperti itu karena hanya melibatkan operasi casting dan push-on-the-stack, yang merupakan operasi tercepat yang dapat dijalankan oleh penerjemah tepat setelah operasi dan booleankondisi bitwise .

Membandingkan ini dengan Dictionary<K, V>, penggunaan memori di sini jauh lebih sedikit: memegang kamus membutuhkan lebih banyak ruang dalam RAM dan beberapa perhitungan lebih banyak oleh CPU untuk membuat dua array (satu untuk kunci dan lainnya untuk nilai) dan mengumpulkan kode hash untuk kunci untuk menempatkan nilai untuk kunci masing-masing.

Jadi, sejauh yang saya tahu, saya tidak percaya bahwa cara yang lebih cepat bisa ada kecuali jika Anda ingin hanya menggunakan if- then- elseblok denganis operator sebagai berikut:

object a = "Hello world";
if (a is string)
{
    // The variable 'a' is a string!
} else if (a is int)
{
    // The variable 'a' is an int!
} // etc.

3

Anda dapat membuat metode kelebihan beban:

void Foo(A a) 
{ 
    a.Hop(); 
}

void Foo(B b) 
{ 
    b.Skip(); 
}

void Foo(object o) 
{ 
    throw new ArgumentException("Unexpected type: " + o.GetType()); 
}

Dan melemparkan argumen untuk dynamicmengetik untuk memotong memeriksa tipe statis:

Foo((dynamic)something);

3

Penyempurnaan pencocokan pola C # 8 memungkinkan untuk melakukannya seperti ini. Dalam beberapa kasus itu melakukan pekerjaan dan lebih ringkas.

        public Animal Animal { get; set; }
        ...
        var animalName = Animal switch
        {
            Cat cat => "Tom",
            Mouse mouse => "Jerry",
            _ => "unknown"
        };

2

Anda sedang mencari Discriminated Unionsyang merupakan fitur bahasa F #, tetapi Anda dapat mencapai efek yang sama dengan menggunakan perpustakaan yang saya buat, yang disebut OneOf

https://github.com/mcintyre321/OneOf

Keuntungan utama atas switch(dan ifdan exceptions as control flow) adalah bahwa ia aman saat kompilasi - tidak ada penangan default atau gagal

void Foo(OneOf<A, B> o)
{
    o.Switch(
        a => a.Hop(),
        b => b.Skip()
    );
}

Jika Anda menambahkan item ketiga ke o, Anda akan mendapatkan kesalahan kompiler karena Anda harus menambahkan Fungsi handler di dalam panggilan sakelar.

Anda juga dapat melakukan hal .Matchyang mengembalikan nilai, daripada mengeksekusi pernyataan:

double Area(OneOf<Square, Circle> o)
{
    return o.Match(
        square => square.Length * square.Length,
        circle => Math.PI * circle.Radius * circle.Radius
    );
}

2

Buat antarmuka IFooable, lalu buat kelas Anda Adan Buntuk menerapkan metode umum, yang pada gilirannya memanggil metode yang sesuai yang Anda inginkan:

interface IFooable
{
    public void Foo();
}

class A : IFooable
{
    //other methods ...

    public void Foo()
    {
        this.Hop();
    }
}

class B : IFooable
{
    //other methods ...

    public void Foo()
    {
        this.Skip();
    }
}

class ProcessingClass
{
    public void Foo(object o)
    {
        if (o == null)
            throw new NullRefferenceException("Null reference", "o");

        IFooable f = o as IFooable;
        if (f != null)
        {
            f.Foo();
        }
        else
        {
            throw new ArgumentException("Unexpected type: " + o.GetType());
        }
    }
}

Perhatikan, bahwa lebih baik menggunakan asalih-alih memeriksa terlebih dahulu isdan kemudian casting, karena dengan begitu Anda membuat 2 gips, jadi lebih mahal.


2

Saya seperti kasus saya biasanya berakhir dengan daftar predikat dan tindakan. Sesuatu di sepanjang garis ini:

class Mine {
    static List<Func<object, bool>> predicates;
    static List<Action<object>> actions;

    static Mine() {
        AddAction<A>(o => o.Hop());
        AddAction<B>(o => o.Skip());
    }

    static void AddAction<T>(Action<T> action) {
        predicates.Add(o => o is T);
        actions.Add(o => action((T)o);
    }

    static void RunAction(object o) {
        for (int i=0; o < predicates.Count; i++) {
            if (predicates[i](o)) {
                actions[i](o);
                break;
            }
        }
    }

    void Foo(object o) {
        RunAction(o);
    }
}

2

Setelah membandingkan opsi beberapa jawaban di sini yang disediakan untuk fitur F #, saya menemukan F # memiliki cara dukungan yang lebih baik untuk switching berbasis tipe (walaupun saya masih berpegang teguh pada C #).
Anda mungkin ingin melihat di sini dan di sini .


2
<masukkan plug untuk F # here>
Overlord Zurg

1

Saya akan membuat antarmuka dengan nama dan metode apa pun yang masuk akal untuk peralihan Anda, sebut saja masing-masing: IDoableyang memberitahu untuk diimplementasikan void Do().

public interface IDoable
{
    void Do();
}

public class A : IDoable
{
    public void Hop() 
    {
        // ...
    }

    public void Do()
    {
        Hop();
    }
}

public class B : IDoable
{
    public void Skip() 
    {
        // ...
    }

    public void Do()
    {
        Skip();
    }
}

dan ubah metode sebagai berikut:

void Foo<T>(T obj)
    where T : IDoable
{
    // ...
    obj.Do();
    // ...
}

Setidaknya dengan itu Anda aman pada saat kompilasi dan saya curiga bahwa kinerja lebih baik daripada memeriksa jenis saat runtime.


1

Dengan C # 8 dan seterusnya, Anda dapat membuatnya lebih ringkas dengan sakelar baru. Dan dengan menggunakan opsi buang _ Anda dapat menghindari membuat variabel innecesary ketika Anda tidak membutuhkannya, seperti ini:

        return document switch {
            Invoice _ => "Is Invoice",
            ShippingList _ => "Is Shipping List",
            _ => "Unknown"
        };

Invoice dan ShippingList adalah kelas dan dokumen adalah objek yang dapat berupa salah satunya.


0

Saya setuju dengan Jon tentang hash tindakan untuk nama kelas. Jika Anda mempertahankan pola Anda, Anda mungkin ingin mempertimbangkan untuk menggunakan konstruk "sebagai":

A a = o as A;
if (a != null) {
    a.Hop();
    return;
}
B b = o as B;
if (b != null) {
    b.Skip();
    return;
}
throw new ArgumentException("...");

Perbedaannya adalah ketika Anda menggunakan derai if (foo is Bar) {((Bar) foo) .Action (); } Anda melakukan casting tipe dua kali. Sekarang mungkin kompiler akan mengoptimalkan dan hanya melakukan itu berfungsi sekali - tetapi saya tidak akan mengandalkannya.


1
Saya benar-benar tidak suka beberapa titik keluar (pengembalian), tetapi jika Anda ingin tetap dengan ini, tambahkan "jika (o == null) lempar" di awal, karena nanti Anda tidak akan tahu apakah para pemain tidak berhasil, atau objek itu nol.
Sunny Milenov

0

Seperti yang Pablo sarankan, pendekatan antarmuka hampir selalu merupakan hal yang benar untuk dilakukan untuk menangani hal ini. Untuk benar-benar menggunakan switch, alternatif lain adalah memiliki custom enum yang menunjukkan tipe Anda di kelas Anda.

enum ObjectType { A, B, Default }

interface IIdentifiable
{
    ObjectType Type { get; };
}
class A : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.A; } }
}

class B : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.B; } }
}

void Foo(IIdentifiable o)
{
    switch (o.Type)
    {
        case ObjectType.A:
        case ObjectType.B:
        //......
    }
}

Ini juga diimplementasikan di BCL. Salah satu contoh adalah MemberInfo.MemberTypes , yang lainnya adalah GetTypeCodeuntuk tipe primitif, seperti:

void Foo(object o)
{
    switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
    {
        case TypeCode.Int16:
        case TypeCode.Int32:
        //etc ......
    }
}

0

Ini adalah jawaban alternatif yang menggabungkan kontribusi dari JaredPar dan VirtLink, dengan batasan-batasan berikut:

  • Konstruksi switch berperilaku sebagai fungsi , dan menerima fungsi sebagai parameter untuk kasus.
  • Memastikan bahwa itu dibangun dengan benar, dan selalu ada fungsi default .
  • Itu kembali setelah pertandingan pertama (benar untuk jawaban JaredPar, tidak berlaku untuk VirtLink satu).

Pemakaian:

 var result = 
   TSwitch<string>
     .On(val)
     .Case((string x) => "is a string")
     .Case((long x) => "is a long")
     .Default(_ => "what is it?");

Kode:

public class TSwitch<TResult>
{
    class CaseInfo<T>
    {
        public Type Target { get; set; }
        public Func<object, T> Func { get; set; }
    }

    private object _source;
    private List<CaseInfo<TResult>> _cases;

    public static TSwitch<TResult> On(object source)
    {
        return new TSwitch<TResult> { 
            _source = source,
            _cases = new List<CaseInfo<TResult>>()
        };
    }

    public TResult Default(Func<object, TResult> defaultFunc)
    {
        var srcType = _source.GetType();
       foreach (var entry in _cases)
            if (entry.Target.IsAssignableFrom(srcType))
                return entry.Func(_source);

        return defaultFunc(_source);
    }

    public TSwitch<TResult> Case<TSource>(Func<TSource, TResult> func)
    {
        _cases.Add(new CaseInfo<TResult>
        {
            Func = x => func((TSource)x),
            Target = typeof(TSource)
        });
        return this;
    }
}

0

Ya - cukup gunakan "pencocokan pola" yang agak aneh bernama dari C # 7 ke atas untuk mencocokkan pada kelas atau struktur:

IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();

switch (concrete1)
{
    case ObjectImplementation1 c1: return "type 1";         
    case ObjectImplementation2 c2: return "type 2";         
}

0

saya menggunakan

    public T Store<T>()
    {
        Type t = typeof(T);

        if (t == typeof(CategoryDataStore))
            return (T)DependencyService.Get<IDataStore<ItemCategory>>();
        else
            return default(T);
    }

0

Harus bekerja dengan

jenis huruf _:

Suka:

int i = 1;
bool b = true;
double d = 1.1;
object o = i; // whatever you want

switch (o)
{
    case int _:
        Answer.Content = "You got the int";
        break;
    case double _:
        Answer.Content = "You got the double";
        break;
    case bool _:
        Answer.Content = "You got the bool";
        break;
}

0

Jika Anda tahu kelas yang Anda harapkan tetapi Anda masih tidak memiliki objek, Anda bahkan dapat melakukan ini:

private string GetAcceptButtonText<T>() where T : BaseClass, new()
{
    switch (new T())
    {
        case BaseClassReview _: return "Review";
        case BaseClassValidate _: return "Validate";
        case BaseClassAcknowledge _: return "Acknowledge";
        default: return "Accept";
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.