Cara membuat Pernyataan C # Switch menggunakan IgnoreCase

92

Jika saya memiliki pernyataan switch-case di mana objek di switch adalah string, apakah mungkin untuk melakukan perbandingan ignoreCase?

Saya punya misalnya:

string s = "house";
switch (s)
{
  case "houSe": s = "window";
}

Akan smendapatkan nilai "window"? Bagaimana cara menimpa pernyataan switch-case sehingga akan membandingkan string menggunakan ignoreCase?

Tolsan
sumber

Jawaban:

64

Seperti yang Anda ketahui, menurunkan dua string dan membandingkannya tidak sama dengan melakukan perbandingan kasus pengabaian. Ada banyak alasan untuk ini. Misalnya, standar Unicode memungkinkan teks dengan diakritik dikodekan dengan berbagai cara. Beberapa karakter menyertakan karakter dasar dan diakritik dalam satu titik kode. Karakter ini juga dapat direpresentasikan sebagai karakter dasar yang diikuti oleh karakter diakritik gabungan. Kedua representasi ini sama untuk semua tujuan, dan perbandingan string yang sadar budaya dalam .NET Framework akan mengidentifikasi mereka dengan benar sebagai sama, baik dengan CurrentCulture atau InvariantCulture (dengan atau tanpa IgnoreCase). Sebaliknya, perbandingan ordinal akan secara keliru menganggapnya tidak setara.

Sayangnya, switchtidak melakukan apa pun kecuali perbandingan ordinal. Perbandingan ordinal baik-baik saja untuk jenis aplikasi tertentu, seperti mem-parsing file ASCII dengan kode yang ditentukan secara kaku, tetapi perbandingan string ordinal salah untuk sebagian besar kegunaan lain.

Apa yang telah saya lakukan di masa lalu untuk mendapatkan perilaku yang benar hanyalah tiruan pernyataan sakelar saya sendiri. Ada banyak cara untuk melakukannya. Salah satu caranya adalah dengan membuat List<T>sepasang string kasus dan delegasi. Daftar tersebut dapat dicari dengan menggunakan perbandingan string yang tepat. Ketika kecocokan ditemukan maka delegasi terkait dapat dipanggil.

Pilihan lainnya adalah melakukan rangkaian ifpernyataan yang jelas . Ini biasanya ternyata tidak seburuk kedengarannya, karena strukturnya sangat teratur.

Hal yang hebat tentang ini adalah bahwa sebenarnya tidak ada penalti kinerja dalam meniru fungsi sakelar Anda sendiri saat membandingkan dengan string. Sistem tidak akan membuat tabel lompat O (1) seperti yang dapat dilakukan dengan bilangan bulat, jadi sistem akan membandingkan setiap string satu per satu.

Jika ada banyak kasus untuk dibandingkan, dan kinerja menjadi masalah, maka List<T>opsi yang dijelaskan di atas dapat diganti dengan kamus yang diurutkan atau tabel hash. Kemudian kinerja berpotensi cocok atau melebihi opsi pernyataan sakelar.

Berikut adalah contoh daftar delegasi:

delegate void CustomSwitchDestination();
List<KeyValuePair<string, CustomSwitchDestination>> customSwitchList;
CustomSwitchDestination defaultSwitchDestination = new CustomSwitchDestination(NoMatchFound);
void CustomSwitch(string value)
{
    foreach (var switchOption in customSwitchList)
        if (switchOption.Key.Equals(value, StringComparison.InvariantCultureIgnoreCase))
        {
            switchOption.Value.Invoke();
            return;
        }
    defaultSwitchDestination.Invoke();
}

Tentu saja, Anda mungkin ingin menambahkan beberapa parameter standar dan mungkin tipe pengembalian ke delegasi CustomSwitchDestination. Dan Anda pasti ingin membuat nama yang lebih baik!

Jika perilaku setiap kasus Anda tidak setuju untuk mendelegasikan pemanggilan dengan cara ini, seperti jika parameter differnt diperlukan, maka Anda terjebak dengan ifpernyataan yang dirantai . Saya juga melakukan ini beberapa kali.

    if (s.Equals("house", StringComparison.InvariantCultureIgnoreCase))
    {
        s = "window";
    }
    else if (s.Equals("business", StringComparison.InvariantCultureIgnoreCase))
    {
        s = "really big window";
    }
    else if (s.Equals("school", StringComparison.InvariantCultureIgnoreCase))
    {
        s = "broken window";
    }
Jeffrey L. Whitledge
sumber
6
Kecuali saya salah, keduanya hanya berbeda untuk budaya tertentu (seperti Turki), dan dalam hal ini dia tidak dapat menggunakan ToUpperInvariant()atau ToLowerInvariant()? Juga, dia tidak membandingkan dua string yang tidak diketahui , dia membandingkan satu string yang tidak diketahui dengan satu string yang dikenal. Jadi, selama dia tahu cara membuat kode keras untuk representasi huruf besar atau kecil yang sesuai maka blok sakelar harus berfungsi dengan baik.
Seth Petry-Johnson
8
@Seth Petry-Johnson - Mungkin pengoptimalan itu dapat dilakukan, tetapi alasan opsi perbandingan string dimasukkan ke dalam kerangka kerja adalah agar kita tidak semua harus menjadi ahli linguistik untuk menulis perangkat lunak yang benar dan dapat diperluas.
Jeffrey L Whitledge
59
BAIK. Saya akan memberikan contoh di mana ini relevan. Misalkan alih-alih "house" kita memiliki kata (bahasa Inggris!) "Café". Nilai ini dapat direpresentasikan dengan baik (dan kemungkinan sama) oleh "caf \ u00E9" atau "kafe \ u0301". Persamaan ordinal (seperti dalam pernyataan switch) dengan salah satu ToLower()atau ToLowerInvariant()akan mengembalikan salah. Equalsdengan StringComparison.InvariantCultureIgnoreCaseakan kembali benar. Karena kedua urutan terlihat identik saat ditampilkan, ToLower()versinya adalah bug yang buruk untuk dilacak. Inilah mengapa selalu yang terbaik adalah melakukan perbandingan senar yang tepat, meskipun Anda bukan orang Turki.
Jeffrey L Whitledge
78

Pendekatan yang lebih sederhana hanya menurunkan string Anda sebelum masuk ke pernyataan switch, dan memiliki case yang lebih rendah.

Sebenarnya, bagian atas sedikit lebih baik dari sudut pandang kinerja nanodetik ekstrim murni, tetapi kurang alami untuk dilihat.

Misalnya:

string s = "house"; 
switch (s.ToLower()) { 
  case "house": 
    s = "window"; 
    break;
}
Nick Craver
sumber
1
Ya, saya mengerti bahwa huruf kecil adalah sebuah cara, tetapi saya ingin dari situ menjadi ignoreCase. Apakah ada cara agar saya dapat mengganti pernyataan switch-case?
Tolsan
6
@Lazarus - Ini dari CLR via C #, ini telah diposting di sini beberapa waktu lalu di thread fitur tersembunyi juga: stackoverflow.com/questions/9033/hidden-features-of-c/… Anda dapat menjalankan LinqPad dengan beberapa juta iterasi, benar.
Nick Craver
1
@Tolsan - Tidak, sayangnya tidak hanya karena sifatnya yang statis. Ada banyak jawaban bagus untuk ini beberapa waktu yang lalu: stackoverflow.com/questions/44905/…
Nick Craver
9
Tampaknya ToUpper(Invariant)tidak hanya lebih cepat, tetapi lebih dapat diandalkan: stackoverflow.com/a/2801521/67824
Ohad Schneider
48

Maaf untuk posting baru ini untuk pertanyaan lama, tetapi ada opsi baru untuk menyelesaikan masalah ini menggunakan C # 7 (VS 2017).

C # 7 sekarang menawarkan "pencocokan pola", dan ini dapat digunakan untuk mengatasi masalah ini sebagai berikut:

string houseName = "house";  // value to be tested, ignoring case
string windowName;   // switch block will set value here

switch (true)
{
    case bool b when houseName.Equals("MyHouse", StringComparison.InvariantCultureIgnoreCase): 
        windowName = "MyWindow";
        break;
    case bool b when houseName.Equals("YourHouse", StringComparison.InvariantCultureIgnoreCase): 
        windowName = "YourWindow";
        break;
    case bool b when houseName.Equals("House", StringComparison.InvariantCultureIgnoreCase): 
        windowName = "Window";
        break;
    default:
        windowName = null;
        break;
}

Solusi ini juga menangani masalah yang disebutkan dalam jawaban oleh @Jeffrey L Whitledge bahwa perbandingan string yang tidak peka huruf besar / kecil tidak sama dengan membandingkan dua string dengan huruf kecil.

Ngomong-ngomong, ada artikel menarik di bulan Februari 2017 di Majalah Visual Studio yang menjelaskan tentang pencocokan pola dan bagaimana cara menggunakannya dalam kasus blok Mohon dilihat: Pencocokan Pola di Blok Kasus C # 7.0

EDIT

Sehubungan dengan jawaban @ LewisM, penting untuk menunjukkan bahwa switchpernyataan tersebut memiliki beberapa perilaku baru yang menarik. Artinya jika casepernyataan Anda berisi deklarasi variabel, maka nilai yang ditentukan di switchbagian tersebut disalin ke variabel yang dideklarasikan di case. Dalam contoh berikut, nilai truedisalin ke variabel lokal b. Selanjutnya, variabel btidak digunakan, dan hanya ada sehingga whenklausa casepernyataan bisa ada:

switch(true)
{
    case bool b when houseName.Equals("X", StringComparison.InvariantCultureIgnoreCase):
        windowName = "X-Window";):
        break;
}

Seperti yang ditunjukkan oleh @LewisM, ini dapat digunakan untuk mendapatkan keuntungan - manfaatnya adalah bahwa hal yang dibandingkan sebenarnya ada dalam switchpernyataan, seperti halnya dengan penggunaan klasik dari switchpernyataan tersebut. Selain itu, nilai sementara yang dideklarasikan dalam casepernyataan dapat mencegah perubahan yang tidak diinginkan atau tidak disengaja ke nilai aslinya:

switch(houseName)
{
    case string hn when hn.Equals("X", StringComparison.InvariantCultureIgnoreCase):
        windowName = "X-Window";
        break;
}
STLDev
sumber
2
Ini akan lebih lama, tetapi saya lebih suka switch (houseName)melakukan perbandingan yang serupa dengan cara Anda melakukannya, yaitucase var name when name.Equals("MyHouse", ...
LewisM
@LewisM - Itu menarik. Bisakah Anda menunjukkan contoh kerja itu?
STLDev
@LewisM - jawaban bagus. Saya telah menambahkan diskusi lebih lanjut tentang penugasan switchnilai argumen ke casevariabel sementara.
STLDev
Yay untuk pencocokan pola di C # modern
Thiago Silva
Anda juga dapat menggunakan "pencocokan pola objek" case { } whensehingga Anda tidak perlu khawatir tentang jenis dan nama variabel.
Bob
33

Dalam beberapa kasus, mungkin ada baiknya menggunakan enum. Jadi pertama-tama parsing enum (dengan flag ignoreCase true) dan kemudian mengaktifkan enum.

SampleEnum Result;
bool Success = SampleEnum.TryParse(inputText, true, out Result);
if(!Success){
     //value was not in the enum values
}else{
   switch (Result) {
      case SampleEnum.Value1:
      break;
      case SampleEnum.Value2:
      break;
      default:
      //do default behaviour
      break;
   }
}
uli78
sumber
Just a Note: Enum TryParse tampaknya tersedia dengan Framework 4.0 dan selanjutnya, FYI. msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx
granadaCoder
4
Saya lebih suka solusi ini karena mencegah penggunaan string ajaib.
pengguna1069816
23

Perpanjangan jawaban oleh @STLDeveloperA. Cara baru untuk melakukan evaluasi pernyataan tanpa beberapa pernyataan if pada c # 7 menggunakan pernyataan Switch pencocokan pola, mirip dengan cara @STLDeveloper meskipun cara ini mengaktifkan variabel yang diaktifkan

string houseName = "house";  // value to be tested
string s;
switch (houseName)
{
    case var name when string.Equals(name, "Bungalow", StringComparison.InvariantCultureIgnoreCase): 
        s = "Single glazed";
    break;

    case var name when string.Equals(name, "Church", StringComparison.InvariantCultureIgnoreCase):
        s = "Stained glass";
        break;
        ...
    default:
        s = "No windows (cold or dark)";
        break;
}

Majalah studio visual memiliki artikel bagus tentang blok kasus pencocokan pola yang mungkin menarik untuk dilihat.

LewisM
sumber
Terima kasih telah menunjukkan fungsionalitas tambahan dari switchpernyataan baru .
STLDev
5
+1 - ini harus menjadi jawaban yang diterima untuk perkembangan modern (C # 7 dan seterusnya). Satu perubahan yang akan saya buat adalah saya akan membuat kode seperti ini: case var name when "Bungalow".Equals(name, StringComparison.InvariantCultureIgnoreCase):karena ini dapat mencegah pengecualian referensi null (di mana houseName adalah null), atau sebagai alternatif menambahkan kasus untuk string menjadi null terlebih dahulu.
Jay
21

Salah satu cara yang mungkin adalah menggunakan kamus kasus abaikan dengan delegasi tindakan.

string s = null;
var dic = new Dictionary<string, Action>(StringComparer.CurrentCultureIgnoreCase)
{
    {"house",  () => s = "window"},
    {"house2", () => s = "window2"}
};

dic["HouSe"]();

// Perhatikan bahwa panggilan tidak mengembalikan teks, tetapi hanya mengisi variabel lokal s.
// Jika Anda ingin mengembalikan teks yang sebenarnya, ganti Actionke Func<string>dan nilai dalam kamus menjadi sesuatu seperti() => "window2"

Magnus
sumber
4
Daripada CurrentCultureIgnoreCase, OrdinalIgnoreCaselebih disukai.
Richard Ev
2
@richardEverett Lebih disukai? Tergantung pada apa yang Anda inginkan, jika Anda menginginkan kasus mengabaikan budaya saat ini tidak disukai.
Magnus
Jika ada yang tertarik, solusi saya (di bawah) mengambil ide ini dan membungkusnya dalam kelas sederhana.
Flydog57
2

Berikut adalah solusi yang membungkus solusi @Magnus dalam sebuah kelas:

public class SwitchCaseIndependent : IEnumerable<KeyValuePair<string, Action>>
{
    private readonly Dictionary<string, Action> _cases = new Dictionary<string, Action>(StringComparer.OrdinalIgnoreCase);

    public void Add(string theCase, Action theResult)
    {
        _cases.Add(theCase, theResult);
    }

    public Action this[string whichCase]
    {
        get
        {
            if (!_cases.ContainsKey(whichCase))
            {
                throw new ArgumentException($"Error in SwitchCaseIndependent, \"{whichCase}\" is not a valid option");
            }
            //otherwise
            return _cases[whichCase];
        }
    }

    public IEnumerator<KeyValuePair<string, Action>> GetEnumerator()
    {
        return _cases.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _cases.GetEnumerator();
    }
}

Berikut adalah contoh penggunaannya di aplikasi Formulir Windows sederhana:

   var mySwitch = new SwitchCaseIndependent
   {
       {"hello", () => MessageBox.Show("hello")},
       {"Goodbye", () => MessageBox.Show("Goodbye")},
       {"SoLong", () => MessageBox.Show("SoLong")},
   };
   mySwitch["HELLO"]();

Jika Anda menggunakan lambda (seperti contoh), Anda mendapatkan penutupan yang akan menangkap variabel lokal Anda (cukup mirip dengan perasaan yang Anda dapatkan dari pernyataan switch).

Karena menggunakan Dictionary di bawah sampulnya, ia mendapat perilaku O (1) dan tidak bergantung pada berjalan melalui daftar string. Tentu saja, Anda perlu membuat kamus itu, dan itu mungkin lebih mahal.

Mungkin masuk akal untuk menambahkan bool ContainsCase(string aCase)metode sederhana yang hanya memanggil metode kamus ContainsKey.

Flydog 57
sumber
1

Saya harap ini membantu mencoba mengubah seluruh string menjadi huruf kecil atau huruf besar dan menggunakan string Huruf kecil untuk perbandingan:

public string ConvertMeasurements(string unitType, string value)
{
    switch (unitType.ToLower())
    {
        case "mmol/l": return (Double.Parse(value) * 0.0555).ToString();
        case "mg/dl": return (double.Parse(value) * 18.0182).ToString();
    }
}
UnknownFellowCoder
sumber
0

Ini harus cukup untuk melakukan ini:

string s = "houSe";
switch (s.ToLowerInvariant())
{
  case "house": s = "window";
  break;
}

Perbandingan sakelar dengan demikian budaya tidak berubah. Sejauh yang saya bisa lihat, ini akan mencapai hasil yang sama seperti solusi Pencocokan Pola C # 7, tetapi lebih ringkas.

Kevin Bennett
sumber
-1

10 tahun kemudian, dengan pencocokan pola C #, Anda dapat melakukan sesuatu seperti:

private string NormalisePropertyType(string propertyType) => true switch
{
    true when string.IsNullOrWhiteSpace(propertyType) => propertyType,
    true when "house".Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "house",
    true when "window".Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "window",
    true when "door".Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "door",
    true when "roof".Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "roof",
    true when "chair".Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "chair",
    _ => propertyType
};
Sudara
sumber