C # Switch İfadesinin IgnoreCase Kullanmasını Sağlama


92

Anahtardaki nesnenin string olduğu bir switch-case deyimim varsa, ignoreCase karşılaştırması yapmak mümkün müdür?

Örneğin var:

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

Will sdeğeri "pencere" olsun? İgnoreCase kullanarak dizeleri karşılaştırması için switch-case ifadesini nasıl geçersiz kılabilirim?

Yanıtlar:


64

Farkında gibi göründüğünüz gibi, iki dizeyi küçültmek ve karşılaştırmak, büyük / küçük harf karşılaştırmasını yok saymakla aynı şey değildir. Bunun pek çok nedeni var. Örneğin, Unicode standardı, aksanlı metnin birden çok şekilde kodlanmasına izin verir. Bazı karakterler, tek bir kod noktasında hem temel karakteri hem de aksan işaretini içerir. Bu karakterler aynı zamanda temel karakter olarak ve ardından bir aksan karakterini birleştirerek temsil edilebilir. Bu iki temsil tüm amaçlar için eşittir ve .NET Framework'teki kültüre duyarlı dize karşılaştırmaları, bunları CurrentCulture veya InvariantCulture (IgnoreCase ile veya onsuz) ile eşit olarak doğru şekilde tanımlayacaktır. Öte yandan, sıralı bir karşılaştırma onları yanlış bir şekilde eşitsiz olarak değerlendirecektir.

Ne yazık ki, switchsıralı bir karşılaştırma dışında hiçbir şey yapmaz. Bir ASCII dosyasını katı şekilde tanımlanmış kodlarla ayrıştırmak gibi belirli uygulama türleri için sıralı bir karşılaştırma iyidir, ancak sıralı dizgi karşılaştırması diğer çoğu kullanım için yanlıştır.

Geçmişte doğru davranışı elde etmek için yaptığım şey sadece kendi switch deyimimi taklit etmekti. Bunu yapmanın birçok yolu var. Bunun bir yolu List<T>, vaka dizileri ve temsilcilerden oluşan bir çift oluşturmaktır . Liste, uygun dizi karşılaştırması kullanılarak aranabilir. Eşleşme bulunduğunda, ilişkili delege çağrılabilir.

Diğer bir seçenek de, açık ififadeler zincirini yapmaktır . Yapı çok düzenli olduğu için bu genellikle göründüğü kadar kötü değildir.

Bununla ilgili harika olan şey, dizelerle karşılaştırırken kendi anahtar işlevselliğinizi taklit etmenin gerçekten herhangi bir performans cezası olmamasıdır. Sistem tamsayılarla yapabildiği gibi bir O (1) atlama tablosu yapmayacak, bu nedenle her dizeyi her seferinde bir tane olmak üzere karşılaştıracak.

Karşılaştırılacak çok sayıda durum varsa ve performans bir sorunsa, List<T>yukarıda açıklanan seçenek sıralı bir sözlük veya karma tablo ile değiştirilebilir. Daha sonra performans, anahtar deyimi seçeneğiyle potansiyel olarak eşleşebilir veya bu seçeneği aşabilir.

Temsilciler listesine bir örnek:

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

Elbette, CustomSwitchDestination temsilcisine bazı standart parametreler ve muhtemelen bir dönüş türü eklemek isteyeceksiniz. Ve daha iyi isimler yapmak isteyeceksiniz!

Vakalarınızın her birinin davranışı, örneğin farklı parametreler gerekliyse, bu şekilde çağrı yetkisi vermeye uygun değilse, zincirlenmiş ififadelerle sıkışıp kalırsınız . Bunu da birkaç kez yaptım.

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

6
Yanılmıyorsam, ikisi sadece belirli kültürler için farklıdır (Türk gibi) ve bu durumda kullanamaz ToUpperInvariant()ToLowerInvariant()? Ayrıca, iki bilinmeyen dizgiyi karşılaştırmıyor, bilinmeyen bir dizgiyi bilinen bir dizeyle karşılaştırıyor. Bu nedenle, uygun büyük veya küçük harf gösterimini nasıl kodlayacağını bildiği sürece, anahtar bloğu iyi çalışmalıdır.
Seth Petry-Johnson

8
@Seth Petry-Johnson - Belki bu optimizasyon yapılabilir, ancak dizi karşılaştırma seçeneklerinin çerçeveye dahil edilmesinin nedeni, doğru, genişletilebilir yazılım yazmak için hepimizin dilbilim uzmanı olmak zorunda kalmamamızdır.
Jeffrey L Whitledge

59
TAMAM. Bunun rölatif olduğu bir örnek vereceğim. "Ev" yerine (İngilizce!) "Kafe" kelimemiz olduğunu varsayalım. Bu değer, "caf \ u00E9" veya "cafe \ u0301" ile eşit derecede iyi (ve eşit olasılıkla) temsil edilebilir. Sıralı eşitlik (switch deyiminde olduğu gibi) ToLower()veya ile ToLowerInvariant()false döndürür. Equalsile StringComparison.InvariantCultureIgnoreCasetrue dönecektir. Her iki sekans da görüntülendiğinde aynı göründüğünden, ToLower()sürüm, izlenmesi gereken kötü bir hatadır. Bu nedenle, Türk olmasanız bile uygun dizgi karşılaştırmaları yapmak her zaman en iyisidir.
Jeffrey L Whitledge

78

Daha basit bir yaklaşım, dizinizi switch deyimine girmeden önce küçültmek ve büyük / küçük harfleri küçültmektir.

Aslında, üst kısım saf aşırı nanosaniye performans açısından biraz daha iyidir, ancak bakmak daha az doğaldır.

Örneğin:

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

1
Evet, küçük harf yapmanın bir yol olduğunu anlıyorum, ancak ondan görmezden gelmek istiyorumCase. Switch-case ifadesini geçersiz kılabilmemin bir yolu var mı?
Tolsan

6
@Lazarus - Bu, CLR'den C # yoluyla, burada bir süre önce gizli özellikler dizisinde de yayınlandı: stackoverflow.com/questions/9033/hidden-features-of-c/… LinqPad'i birkaç tane ile çalıştırabilirsiniz milyon yineleme, doğrudur.
Nick Craver

1
@Tolsan - Hayır, maalesef sadece statik olduğu için değil. Bir süre önce bununla ilgili güzel bir yanıt grubu vardı: stackoverflow.com/questions/44905/…
Nick Craver

9
Görünüşe göre ToUpper(Invariant)sadece daha hızlı değil, aynı zamanda daha güvenilir: stackoverflow.com/a/2801521/67824
Ohad Schneider


48

Eski bir soruya bu yeni gönderi için özür dilerim, ancak bu sorunu C # 7 (VS 2017) kullanarak çözmek için yeni bir seçenek var.

C # 7 artık "desen eşleştirme" sunuyor ve bu sorunu şu şekilde ele almak için kullanılabilir:

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

Bu çözüm aynı zamanda @ Jeffrey L Whitledge'ın yanıtında belirtilen, dizelerin büyük / küçük harfe duyarlı olmayan karşılaştırmasının iki küçük harfli dizgiyi karşılaştırmakla aynı olmadığı sorununu da ele alıyor.

Bu arada, Şubat 2017'de Visual Studio Magazine'de desen eşleştirmesini ve kasa bloklarında nasıl kullanılabileceğini anlatan ilginç bir makale vardı. Lütfen bir göz atın: C # 7.0 Durum Bloklarında Desen Eşleştirme

DÜZENLE

@ LewisM'in cevabının ışığında, switchifadenin bazı yeni, ilginç davranışları olduğunu belirtmek önemlidir . Diğer bir deyişle, caseifadeniz bir değişken bildirimi içeriyorsa , bu durumda , switchbölümde belirtilen değer case,. Aşağıdaki örnekte, değer trueyerel değişkene kopyalanmıştır b. Ayrıca buna, değişken bkullanılmayan olduğunu ve sadece bu var whenhiç fıkra caseaçıklamada bulunabilir:

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

@LewisM'in belirttiği gibi, bu fayda sağlamak için kullanılabilir - bu, karşılaştırılan şeyin aslında switchifadede olduğu gibi , ifadenin klasik kullanımında olduğu gibi fayda sağlar switch. Ayrıca, caseifadede bildirilen geçici değerler , orijinal değerde istenmeyen veya yanlışlıkla yapılan değişiklikleri önleyebilir:

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

2
Daha uzun olurdu, ancak switch (houseName)karşılaştırmayı sizin yaptığınıza benzer şekilde yapmayı tercih ederim , yanicase var name when name.Equals("MyHouse", ...
LewisM

@LewisM - Bu ilginç. Bunun çalışan bir örneğini gösterebilir misiniz?
STLDev

@LewisM - harika cevap. Bağımsız switchdeğişken değerlerinin casegeçici değişkenlere atanması hakkında daha fazla tartışma ekledim .
STLDev

Modern C # 'da kalıp eşleştirme için Yay
Thiago Silva

case { } whenDeğişken türü ve adı hakkında endişelenmenize gerek kalmaması için "nesne kalıbı eşleştirme" yi de bu şekilde kullanabilirsiniz.
Bob

33

Bazı durumlarda bir sıralama kullanmak iyi bir fikir olabilir. Bu nedenle, ilk önce numaralandırmayı (ignoreCase bayrağı true ile) ayrıştırın ve ardından numaralandırmada bir anahtarın olmasını sağlayın.

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

Bir Not: Enum TryParse, FYI, Framework 4.0 ve ilerisi ile kullanılabilir görünüyor. msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx
granadaCoder

4
Bu çözümü sihirli iplerin kullanılmasını engellediği için tercih ediyorum.
user1069816

23

@STLDeveloperA tarafından yanıtın bir uzantısı. C # 7'den itibaren çoklu if ifadeleri olmadan ifade değerlendirmesi yapmanın yeni bir yolu, @STLDeveloper yöntemine benzer şekilde desen eşleştirme Switch ifadesini kullanmaktır, ancak bu yol değiştirilen değişkeni açar

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

Görsel stüdyo dergisinde, göz atmaya değer olabilecek desen eşleştirme vaka blokları hakkında güzel bir makale var .


Yeni switchifadenin ek işlevlerine işaret ettiğiniz için teşekkür ederiz .
STLDev

5
+1 - bu, modern (C # 7'den itibaren) geliştirme için kabul edilen yanıt olmalıdır. case var name when "Bungalow".Equals(name, StringComparison.InvariantCultureIgnoreCase):Yapacağım bir değişiklik, şu şekilde kodlayacağımdır : çünkü bu, boş bir referans istisnasını önleyebilir (evAdı boştur) veya alternatif olarak ilk önce dize için bir durum ekleyebilir.
Jay

21

Olası bir yol, bir eylem temsilcisiyle birlikte yok sayma durumu sözlüğü kullanmaktır.

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

dic["HouSe"]();

// Çağrının metin döndürmediğini, yalnızca yerel değişkenleri doldurduğunu unutmayın.
// Eğer gerçek metni döndürmek değiştirmek istiyorsanız Actionetmek Func<string>gibi bir şeye sözlükte ve değerler() => "window2"


4
Yerine CurrentCultureIgnoreCase, OrdinalIgnoreCasetercih edilir.
Richard Ev

2
@richardEverett Tercih ettiniz mi? Ne istediğinize bağlı, mevcut kültürü görmezden gelmek istiyorsanız tercih edilmez.
Magnus

İlgilenen varsa, çözümüm (aşağıda) bu fikri alır ve basit bir sınıfa sarar.
Flydog57

2

İşte bir sınıfta @Magnus'un çözümünü saran bir çözüm:

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

İşte basit bir Windows Form uygulamasında kullanmanın bir örneği:

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

Lambdas kullanırsanız (örnekte olduğu gibi), yerel değişkenlerinizi yakalayan kapanışlar elde edersiniz (bir switch ifadesinden aldığınız hisse oldukça yakın).

Kapakların altında Sözlük kullandığından, O (1) davranışı alır ve dizeler listesinde gezinmeye dayanmaz. Elbette, bu sözlüğü oluşturmanız gerekiyor ve bu muhtemelen daha pahalıya mal oluyor.

bool ContainsCase(string aCase)Basitçe sözlüğün ContainsKeyyöntemini çağıran basit bir yöntem eklemek muhtemelen mantıklı olacaktır .


1

Umarım bu, tüm dizeyi küçük harfe veya Büyük harfe dönüştürmeye ve karşılaştırma için Küçük harf dizesini kullanmaya yardımcı olur:

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

0

Bunu yapmak yeterli olmalı:

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

Anahtar karşılaştırması bu nedenle kültürde değişmezdir. Görebildiğim kadarıyla bu, C # 7 Örüntü Eşleştirme çözümleriyle aynı sonucu elde etmeli, ancak daha kısaca.


-1

10 yıl sonra, C # kalıp eşleştirmeyle aşağıdaki gibi bir şey yapabilirsiniz:

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
};
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.