วิธีสร้าง C # Switch Statement ใช้ IgnoreCase


92

ถ้าฉันมีคำสั่ง switch-case ที่อ็อบเจ็กต์ในสวิตช์เป็นสตริงเป็นไปได้ไหมที่จะทำการเปรียบเทียบ IgnoreCase

ฉันมีตัวอย่างเช่น:

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

จะsได้ค่า "window"? ฉันจะแทนที่คำสั่ง switch-case ได้อย่างไรจึงจะเปรียบเทียบสตริงโดยใช้ IgnoreCase

คำตอบ:


64

ตามที่คุณทราบแล้วการลดขนาดสองสตริงและการเปรียบเทียบไม่เหมือนกับการเปรียบเทียบตัวพิมพ์เล็กและใหญ่ มีเหตุผลมากมายสำหรับเรื่องนี้ ตัวอย่างเช่นมาตรฐาน Unicode อนุญาตให้เข้ารหัสข้อความที่มีเครื่องหมายกำกับเสียงได้หลายวิธี อักขระบางตัวมีทั้งอักขระพื้นฐานและตัวกำกับเสียงในจุดรหัสเดียว อักขระเหล่านี้อาจแสดงเป็นอักขระพื้นฐานตามด้วยอักขระกำกับเสียง การแสดงทั้งสองนี้มีค่าเท่ากันสำหรับวัตถุประสงค์ทั้งหมดและการเปรียบเทียบสตริงที่รับรู้วัฒนธรรมใน. NET Framework จะระบุได้อย่างถูกต้องว่าเท่าเทียมกันโดยใช้ CurrentCulture หรือ InvariantCulture (มีหรือไม่มี IgnoreCase) ในทางกลับกันการเปรียบเทียบลำดับจะถือว่าไม่เท่ากันอย่างไม่ถูกต้อง

น่าเสียดายที่switchไม่ได้ทำอะไรเลยนอกจากการเปรียบเทียบลำดับ การเปรียบเทียบลำดับนั้นใช้ได้ดีสำหรับแอปพลิเคชันบางประเภทเช่นการแยกวิเคราะห์ไฟล์ ASCII ที่มีรหัสที่กำหนดไว้อย่างเข้มงวด แต่การเปรียบเทียบสตริงลำดับจะผิดสำหรับการใช้งานอื่น ๆ ส่วนใหญ่

สิ่งที่ฉันได้ทำในอดีตเพื่อให้ได้พฤติกรรมที่ถูกต้องเป็นเพียงการล้อเลียนคำสั่งสวิตช์ของฉันเอง มีหลายวิธีในการทำเช่นนี้ วิธีหนึ่งคือการสร้างList<T>สตริงเคสและผู้รับมอบสิทธิ์คู่หนึ่ง สามารถค้นหารายการได้โดยใช้การเปรียบเทียบสตริงที่เหมาะสม เมื่อพบการจับคู่แล้วอาจมีการเรียกตัวแทนที่เกี่ยวข้อง

อีกทางเลือกหนึ่งคือการสร้างห่วงโซ่ของifข้อความที่ชัดเจน สิ่งนี้มักจะไม่เลวร้ายอย่างที่คิดเนื่องจากโครงสร้างเป็นปกติมาก

สิ่งที่ยอดเยี่ยมเกี่ยวกับเรื่องนี้คือไม่มีบทลงโทษด้านประสิทธิภาพในการจำลองฟังก์ชันสวิตช์ของคุณเองเมื่อเปรียบเทียบกับสตริง ระบบจะไม่สร้างตารางกระโดด O (1) แบบที่ทำได้ด้วยจำนวนเต็มดังนั้นจึงจะเปรียบเทียบแต่ละสตริงทีละรายการ

หากมีหลายกรณีที่ต้องเปรียบเทียบและประสิทธิภาพเป็นปัญหาList<T>ตัวเลือกที่อธิบายไว้ข้างต้นอาจถูกแทนที่ด้วยพจนานุกรมที่เรียงลำดับหรือตารางแฮช จากนั้นประสิทธิภาพอาจตรงหรือเกินตัวเลือกคำสั่งสวิตช์

นี่คือตัวอย่างของรายชื่อผู้ได้รับมอบหมาย:

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

แน่นอนคุณอาจต้องการเพิ่มพารามิเตอร์มาตรฐานและอาจเป็นประเภทส่งคืนไปยังผู้รับมอบสิทธิ์ CustomSwitchDestination และคุณจะต้องสร้างชื่อให้ดีขึ้น!

หากลักษณะการทำงานของแต่ละกรณีของคุณไม่คล้อยตามการมอบหมายการภาวนาในลักษณะนี้เช่นถ้าพารามิเตอร์ differnt มีความจำเป็นแล้วคุณจะติดอยู่กับล่ามโซ่ifstatments ฉันเคยทำแบบนี้มาแล้วหลายครั้ง

    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
เว้นแต่ฉันจะเข้าใจผิดทั้งสองต่างกันเพียงบางวัฒนธรรมเท่านั้น (เช่นตุรกี) และในกรณีนั้นเขาใช้ไม่ได้ToUpperInvariant()หรือToLowerInvariant()? นอกจากนี้เขาไม่ได้เปรียบเทียบสตริงที่ไม่รู้จักสองสตริงเขากำลังเปรียบเทียบสตริงที่ไม่รู้จักหนึ่งสตริงกับสตริงที่รู้จักกัน ดังนั้นตราบใดที่เขารู้วิธีฮาร์ดโค้ดการแทนตัวพิมพ์ใหญ่หรือตัวพิมพ์เล็กที่เหมาะสมบล็อกสวิตช์ก็ควรทำงานได้ดี
Seth Petry-Johnson

8
@Seth Petry-Johnson - บางทีการเพิ่มประสิทธิภาพนั้นอาจเกิดขึ้นได้ แต่เหตุผลที่ตัวเลือกการเปรียบเทียบสตริงถูกรวมไว้ในกรอบดังนั้นเราทุกคนไม่จำเป็นต้องเป็นผู้เชี่ยวชาญด้านภาษาศาสตร์เพื่อเขียนซอฟต์แวร์ที่ถูกต้องและขยายได้
Jeffrey L Whitledge

59
ตกลง. ฉันจะยกตัวอย่างที่น่าเชื่อถือ สมมติว่าแทนที่จะเป็น "บ้าน" เรามีคำว่า "café" (ภาษาอังกฤษ!) ค่านี้สามารถแสดงได้ดีเท่า ๆ กัน (และมีโอกาสเท่ากัน) โดย "caf \ u00E9" หรือ "cafe \ u0301" ความเท่าเทียมกันตามลำดับ (เช่นเดียวกับคำสั่งสวิตช์) ที่มีToLower()หรือToLowerInvariant()จะคืนค่าเป็นเท็จ Equalsด้วยStringComparison.InvariantCultureIgnoreCaseจะกลับมาจริง เนื่องจากทั้งสองลำดับมีลักษณะเหมือนกันเมื่อแสดงToLower()เวอร์ชันจึงเป็นข้อบกพร่องที่น่ารังเกียจในการติดตาม นี่คือเหตุผลที่ดีที่สุดที่จะทำการเปรียบเทียบสตริงที่เหมาะสมแม้ว่าคุณจะไม่ใช่คนตุรกีก็ตาม
Jeffrey L Whitledge

78

วิธีการที่ง่ายกว่าคือการลดขนาดสตริงของคุณก่อนที่จะเข้าสู่คำสั่งสวิตช์และให้ตัวพิมพ์เล็กลง

อันที่จริงส่วนบนดีกว่าเล็กน้อยจากจุดยืนด้านประสิทธิภาพระดับนาโนวินาทีที่บริสุทธิ์มาก แต่ดูเป็นธรรมชาติน้อยกว่า

เช่น:

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

1
ใช่ฉันเข้าใจว่าการลดขนาดเป็นวิธีการหนึ่ง แต่ฉันต้องการให้เป็นกรณีที่ไม่สนใจ มีวิธีที่ฉันสามารถแทนที่คำสั่ง switch-case ได้หรือไม่?
Tolsan

6
@Lazarus - นี่มาจาก CLR ผ่าน C # ซึ่งมันถูกโพสต์ไว้ที่นี่สักครู่ในเธรดคุณสมบัติที่ซ่อนอยู่เช่นกัน: stackoverflow.com/questions/9033/hidden-features-of-c/… คุณสามารถเปิด LinqPad ได้ด้วยไม่กี่ นับล้านซ้ำถือเป็นจริง
Nick Craver

1
@Tolsan - ไม่น่าเสียดายที่ไม่ใช่เพียงเพราะมันเป็นธรรมชาติที่คงที่ มีคำตอบที่ดีสำหรับเรื่องนี้ในขณะนี้: stackoverflow.com/questions/44905/…
Nick Craver

9
ดูเหมือนว่าToUpper(Invariant)จะไม่เร็วขึ้นเท่านั้น แต่ยังมีความน่าเชื่อถือมากกว่า: stackoverflow.com/a/2801521/67824
Ohad Schneider


48

ขออภัยสำหรับโพสต์ใหม่นี้เป็นคำถามเก่า แต่มีตัวเลือกใหม่สำหรับการแก้ปัญหานี้โดยใช้ C # 7 (VS 2017)

ตอนนี้ C # 7 มี "การจับคู่รูปแบบ" และสามารถใช้เพื่อแก้ไขปัญหานี้ได้ดังนี้:

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

โซลูชันนี้ยังเกี่ยวข้องกับปัญหาที่กล่าวถึงในคำตอบของ @Jeffrey L Whitledge ว่าการเปรียบเทียบสตริงแบบไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่ไม่เหมือนกับการเปรียบเทียบสตริงที่มีตัวพิมพ์เล็กสองตัว

อย่างไรก็ตามมีบทความที่น่าสนใจในเดือนกุมภาพันธ์ 2017 ในนิตยสาร Visual Studio ซึ่งอธิบายถึงการจับคู่รูปแบบและวิธีใช้ในกรณีบล็อก โปรดดู: การจับคู่รูปแบบใน C # 7.0 Case Blocks

แก้ไข

จากคำตอบของ @ LewisM สิ่งสำคัญคือต้องชี้ให้เห็นว่าswitchข้อความดังกล่าวมีพฤติกรรมใหม่ ๆ ที่น่าสนใจ นั่นคือถ้าcaseคำสั่งของคุณมีการประกาศตัวแปรค่าที่ระบุในswitchส่วนจะถูกคัดลอกไปยังตัวแปรที่ประกาศในcase. ในตัวอย่างต่อไปค่าที่ถูกคัดลอกลงตัวแปรท้องถิ่นtrue bยิ่งไปกว่านั้นตัวแปรbจะไม่ได้ใช้งานและมีอยู่เพื่อให้whenอนุประโยคของประโยคcaseคำสั่งนั้นมีอยู่เท่านั้น:

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

ดังที่ @LewisM ชี้ให้เห็นสิ่งนี้สามารถนำมาใช้ให้เกิดประโยชน์ - ประโยชน์ที่ว่าสิ่งที่ถูกเปรียบเทียบนั้นมีอยู่จริงในswitchคำแถลงเช่นเดียวกับการใช้switchคำสั่งแบบคลาสสิก นอกจากนี้ค่าชั่วคราวที่ประกาศในcaseคำสั่งสามารถป้องกันการเปลี่ยนแปลงที่ไม่ต้องการหรือโดยไม่ได้ตั้งใจกับค่าเดิม:

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

2
มันจะนานกว่านี้ แต่ฉันอยากจะswitch (houseName)ทำการเปรียบเทียบแบบเดียวกับที่คุณทำเช่นcase var name when name.Equals("MyHouse", ...
LewisM

@LewisM - น่าสนใจ คุณสามารถแสดงตัวอย่างการทำงานได้หรือไม่?
STLDev

@LewisM - คำตอบที่ดี ฉันได้เพิ่มการอภิปรายเพิ่มเติมเกี่ยวกับการกำหนดswitchค่าอาร์กิวเมนต์ให้กับcaseตัวแปรชั่วคราว
STLDev

ใช่สำหรับการจับคู่รูปแบบใน C # สมัยใหม่
Thiago Silva

คุณยังสามารถใช้ "การจับคู่รูปแบบวัตถุ" ได้เช่นกันcase { } whenดังนั้นคุณจึงไม่ต้องกังวลเกี่ยวกับประเภทและชื่อตัวแปร
Bob

33

ในบางกรณีอาจเป็นความคิดที่ดีที่จะใช้ enum ดังนั้นก่อนอื่นให้แยกวิเคราะห์ enum (ด้วยค่าสถานะ IgnoreCase true) และมีสวิตช์บน 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;
   }
}

หมายเหตุ: Enum TryParse ดูเหมือนจะพร้อมใช้งานกับ Framework 4.0 และส่งต่อ FYI msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx
granadaCoder

4
ฉันชอบวิธีแก้ปัญหานี้เนื่องจากไม่สนับสนุนการใช้สายเวทย์มนตร์
user1069816

23

ส่วนขยายของคำตอบโดย @STLDeveloperA วิธีใหม่ในการประเมินคำสั่งโดยไม่ต้องใช้คำสั่ง if หลาย ๆ คำใน c # 7 คือการใช้คำสั่ง Switch ที่ตรงกับรูปแบบซึ่งคล้ายกับวิธีที่ @STLDeveloper แม้ว่าวิธีนี้จะสลับกับตัวแปรที่กำลังเปลี่ยน

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

นิตยสารวิชวลสตูดิโอมีบทความที่ดีเกี่ยวกับรูปแบบการจับคู่เคสที่อาจคุ้มค่ากับการดู


ขอขอบคุณที่ชี้ให้เห็นฟังก์ชันเพิ่มเติมของswitchคำสั่งใหม่
STLDev

5
+1 - นี่ควรเป็นคำตอบที่ได้รับการยอมรับสำหรับการพัฒนาสมัยใหม่ (C # 7 เป็นต้นไป) การเปลี่ยนแปลงอย่างหนึ่งที่ฉันจะทำคือฉันจะเขียนโค้ดเช่นนี้case var name when "Bungalow".Equals(name, StringComparison.InvariantCultureIgnoreCase):เนื่องจากสามารถป้องกันข้อยกเว้นการอ้างอิงที่เป็นโมฆะ (โดยที่ houseName เป็นโมฆะ) หรืออีกวิธีหนึ่งคือเพิ่มกรณีสำหรับสตริงที่เป็นโมฆะก่อน
เจ

21

วิธีหนึ่งที่เป็นไปได้คือการใช้พจนานุกรมกรณีละเว้นกับผู้มอบหมายการดำเนินการ

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

dic["HouSe"]();

// โปรดทราบว่าการโทรไม่ส่งคืนข้อความ แต่จะเติมเฉพาะตัวแปรท้องถิ่น s
// ถ้าคุณต้องการที่จะกลับข้อความจริงเปลี่ยนActionไปFunc<string>และค่าในพจนานุกรมเพื่อสิ่งที่ต้องการ() => "window2"


4
แทนที่จะCurrentCultureIgnoreCase, OrdinalIgnoreCaseเป็นที่ต้องการ
Richard Ev

2
@richardEverett ที่ต้องการ? ขึ้นอยู่กับสิ่งที่คุณต้องการหากคุณต้องการกรณีที่ไม่สนใจวัฒนธรรมปัจจุบันก็ไม่เป็นที่ต้องการ
Magnus

หากใครสนใจวิธีแก้ปัญหาของฉัน (ด้านล่าง) จะนำแนวคิดนี้ไปรวมไว้ในชั้นเรียนง่ายๆ
Flydog57

2

นี่คือโซลูชันที่รวมโซลูชันของ @Magnus ไว้ในคลาส:

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

นี่คือตัวอย่างการใช้งานในแอป Windows Form อย่างง่าย:

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

หากคุณใช้ lambdas (เช่นตัวอย่าง) คุณจะได้รับการปิดซึ่งจะจับตัวแปรในพื้นที่ของคุณ (ค่อนข้างใกล้เคียงกับความรู้สึกที่คุณได้รับจากคำสั่งสวิตช์)

เนื่องจากใช้พจนานุกรมใต้ฝาครอบจึงมีพฤติกรรม O (1) และไม่ต้องอาศัยการเดินผ่านรายการสตริง แน่นอนคุณต้องสร้างพจนานุกรมนั้นและอาจมีค่าใช้จ่ายมากขึ้น

อาจเป็นเรื่องที่สมเหตุสมผลที่จะเพิ่มbool ContainsCase(string aCase)วิธีง่ายๆที่เรียกว่าContainsKeyวิธีการของพจนานุกรม


1

ฉันหวังว่านี่จะช่วยในการลองแปลงสตริงทั้งหมดเป็นตัวพิมพ์เล็กหรือตัวพิมพ์ใหญ่และใช้สตริงตัวพิมพ์เล็กเพื่อเปรียบเทียบ:

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

ควรจะเพียงพอที่จะทำสิ่งนี้:

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

การเปรียบเทียบสวิตช์จึงทำให้วัฒนธรรมไม่เปลี่ยนแปลง เท่าที่ฉันเห็นสิ่งนี้น่าจะได้ผลลัพธ์เช่นเดียวกับโซลูชันการจับคู่รูปแบบ C # 7 แต่จะรวบรัดกว่า


-1

10 ปีต่อมาด้วยการจับคู่รูปแบบ C # คุณสามารถทำสิ่งต่อไปนี้

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
};
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.