Làm thế nào để sử dụng Câu lệnh C # Switch Statement


89

Nếu tôi có một câu lệnh switch-case trong đó đối tượng trong switch là chuỗi, thì có thể thực hiện một phép so sánh ignoreCase không?

Tôi có ví dụ:

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

Sẽ snhận được giá trị "cửa sổ"? Làm cách nào để ghi đè câu lệnh switch-case để nó so sánh các chuỗi bằng cách sử dụng ignoreCase?

Câu trả lời:


63

Như bạn đã biết, viết thường hai chuỗi và so sánh chúng không giống như thực hiện so sánh chữ hoa và chữ thường. Có rất nhiều lý do cho điều này. Ví dụ, tiêu chuẩn Unicode cho phép văn bản có dấu được mã hóa theo nhiều cách. Một số ký tự bao gồm cả ký tự cơ sở và dấu phụ trong một điểm mã. Các ký tự này cũng có thể được biểu diễn dưới dạng ký tự cơ sở theo sau là ký tự dấu phụ kết hợp. Hai cách đại diện này ngang nhau cho tất cả các mục đích và các so sánh chuỗi nhận biết văn hóa trong .NET Framework sẽ xác định chính xác chúng là bằng nhau, với CurrentCulture hoặc InvariantCulture (có hoặc không có lờ mờ). Mặt khác, so sánh theo thứ tự sẽ không chính xác coi chúng là không bình đẳng.

Thật không may, switchkhông làm bất cứ điều gì ngoài một so sánh thứ tự. So sánh thứ tự là tốt cho một số loại ứng dụng, chẳng hạn như phân tích cú pháp tệp ASCII với các mã được xác định chặt chẽ, nhưng so sánh chuỗi thứ tự là sai đối với hầu hết các mục đích sử dụng khác.

Những gì tôi đã làm trong quá khứ để có được hành vi chính xác chỉ là mô phỏng tuyên bố chuyển đổi của chính tôi. Có rất nhiều cách để làm điều này. Một cách sẽ là tạo một List<T>cặp chuỗi trường hợp và đại biểu. Danh sách có thể được tìm kiếm bằng cách so sánh chuỗi thích hợp. Khi kết quả phù hợp được tìm thấy thì đại biểu được liên kết có thể được gọi.

Một lựa chọn khác là thực hiện chuỗi ifcâu lệnh rõ ràng . Điều này thường hóa ra không quá tệ vì cấu trúc này rất đều đặn.

Điều tuyệt vời về điều này là thực sự không có bất kỳ hình phạt hiệu suất nào trong việc chế nhạo chức năng chuyển đổi của riêng bạn khi so sánh với các chuỗi. Hệ thống sẽ không tạo bảng nhảy O (1) theo cách mà nó có thể làm với các số nguyên, vì vậy dù sao thì nó cũng sẽ so sánh từng chuỗi một.

Nếu có nhiều trường hợp cần so sánh và hiệu suất là một vấn đề, thì List<T>tùy chọn được mô tả ở trên có thể được thay thế bằng từ điển được sắp xếp hoặc bảng băm. Khi đó, hiệu suất có thể khớp hoặc vượt quá tùy chọn câu lệnh chuyển đổi.

Dưới đây là một ví dụ về danh sách các đại biểu:

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

Tất nhiên, bạn có thể sẽ muốn thêm một số tham số tiêu chuẩn và có thể là kiểu trả về cho đại biểu CustomSwitchDestination. Và bạn sẽ muốn tạo ra những cái tên hay hơn!

Nếu hành vi của mỗi trường hợp của bạn không thể ủy quyền lệnh gọi theo cách này, chẳng hạn như nếu các thông số khác nhau là cần thiết, thì bạn đang mắc kẹt với các trạng thái bị xích if. Tôi cũng đã làm điều này một vài lần.

    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
Trừ khi tôi nhầm lẫn, cả hai chỉ khác nhau đối với một số nền văn hóa nhất định (như tiếng Thổ Nhĩ Kỳ), và trong trường hợp đó anh ta không thể sử dụng ToUpperInvariant()hoặc ToLowerInvariant()? Ngoài ra, anh ta không so sánh hai chuỗi không xác định , anh ta so sánh một chuỗi không xác định với một chuỗi đã biết. Do đó, miễn là anh ta biết cách mã hóa biểu diễn chữ hoa hoặc chữ thường phù hợp thì khối chuyển đổi sẽ hoạt động tốt.
Seth Petry-Johnson

8
@Seth Petry-Johnson - Có lẽ có thể thực hiện tối ưu hóa đó, nhưng lý do các tùy chọn so sánh chuỗi được đưa vào khuôn khổ là vì vậy tất cả chúng ta không cần phải trở thành chuyên gia ngôn ngữ học để viết phần mềm chính xác, có thể mở rộng.
Jeffrey L Whitledge

54
ĐỒNG Ý. Tôi sẽ đưa ra một ví dụ mà điều này có liên quan. Giả sử thay vì "house", chúng ta có từ (tiếng Anh!) "Café". Giá trị này có thể được thể hiện tốt như nhau (và có khả năng như nhau) bởi "caf \ u00E9" hoặc "cafe \ u0301". Bình đẳng thứ tự (như trong câu lệnh switch) với một trong hai ToLower()hoặc ToLowerInvariant()sẽ trả về false. Equalsvới StringComparison.InvariantCultureIgnoreCasesẽ trả về true. Vì cả hai chuỗi trông giống hệt nhau khi hiển thị, ToLower()phiên bản này là một lỗi khó theo dõi. Đây là lý do tại sao tốt nhất bạn nên so sánh chuỗi thích hợp, ngay cả khi bạn không phải là người Thổ Nhĩ Kỳ.
Jeffrey L Whitledge

77

Một cách tiếp cận đơn giản hơn chỉ là viết thường chuỗi của bạn trước khi nó đi vào câu lệnh switch và đặt các trường hợp thấp hơn.

Trên thực tế, phần trên tốt hơn một chút từ quan điểm hiệu suất nano giây thuần túy, nhưng nhìn kém tự nhiên hơn.

Ví dụ:

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

1
Có, tôi hiểu rằng viết thường là một cách, nhưng tôi muốn từ đó là lờCase. Có cách nào để tôi có thể ghi đè câu lệnh switch-case không?
Tolsan

6
@Lazarus - Đây là từ CLR thông qua C #, nó đã được đăng ở đây một thời gian trở lại trong chuỗi các tính năng ẩn: stackoverflow.com/questions/9033/hidden-features-of-c/… Bạn có thể kích hoạt LinqPad bằng một vài triệu lần lặp, đúng.
Nick Craver

1
@Tolsan - Không, tiếc là không chỉ vì bản chất tĩnh của nó. Có một loạt câu trả lời hay về vấn đề này một thời gian trước: stackoverflow.com/questions/44905/…
Nick Craver

9
Nó xuất hiện ToUpper(Invariant)không chỉ nhanh hơn, nhưng đáng tin cậy hơn: stackoverflow.com/a/2801521/67824
Ohad Schneider


48

Xin lỗi vì bài viết mới này cho một câu hỏi cũ, nhưng có một tùy chọn mới để giải quyết vấn đề này bằng cách sử dụng C # 7 (VS 2017).

C # 7 hiện cung cấp "đối sánh mẫu" và nó có thể được sử dụng để giải quyết vấn đề này do đó:

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

Giải pháp này cũng giải quyết vấn đề được đề cập trong câu trả lời của @Jeffrey L Whitledge rằng so sánh phân biệt chữ hoa chữ thường của các chuỗi không giống như so sánh hai chuỗi có chữ thường.

Nhân tiện, có một bài báo thú vị vào tháng 2 năm 2017 trên Tạp chí Visual Studio mô tả việc đối sánh mẫu và cách nó có thể được sử dụng trong các khối trường hợp. Vui lòng xem: Khớp mẫu trong Khối trường hợp C # 7.0

BIÊN TẬP

Dựa trên câu trả lời của @ LewisM, điều quan trọng là chỉ ra rằng switchtuyên bố có một số hành vi mới, thú vị. Đó là nếu casecâu lệnh của bạn chứa một khai báo biến, thì giá trị được chỉ định trong switchphần sẽ được sao chép vào biến được khai báo trong case. Trong ví dụ sau, giá trị trueđược sao chép vào biến cục bộ b. Hơn nữa, biến bkhông được sử dụng và chỉ tồn tại để whenmệnh đề của casecâu lệnh có thể tồn tại:

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

Như @LewisM đã chỉ ra, điều này có thể được sử dụng để mang lại lợi ích - lợi ích đó là thứ được so sánh thực sự có trong switchcâu lệnh, giống như cách sử dụng cổ điển của switchcâu lệnh. Ngoài ra, các giá trị tạm thời được khai báo trong casecâu lệnh có thể ngăn chặn các thay đổi không mong muốn hoặc vô tình đối với giá trị ban đầu:

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

2
Nó sẽ lâu hơn, nhưng tôi muốn switch (houseName)sau đó làm phép so sánh tương tự như cách bạn đã làm, tức làcase var name when name.Equals("MyHouse", ...
LewisM

@LewisM - Thật thú vị. Bạn có thể chỉ ra một ví dụ làm việc về điều đó?
STLDev

@LewisM - câu trả lời tuyệt vời. Tôi đã thảo luận thêm về việc gán switchgiá trị đối số cho casecác biến tạm thời.
STLDev

Yay cho phù hợp với mô hình trong hiện đại C #
Thiago Silva

Bạn cũng có thể sử dụng "đối sánh mẫu đối tượng" như vậy case { } whenđể bạn không phải lo lắng về kiểu và tên biến.
Bob

32

Trong một số trường hợp, bạn có thể sử dụng enum. Vì vậy, trước tiên hãy phân tích cú pháp enum (với cờ ignoreCase true) và hơn là có một công tắc trên 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;
   }
}

Chỉ cần lưu ý: Enum TryParse dường như có sẵn với Framework 4.0 trở đi, FYI. msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx
granadaCoder

4
Tôi thích giải pháp này hơn vì nó không khuyến khích sử dụng dây ma thuật.
user1069816

22

Phần mở rộng cho câu trả lời của @STLDeveloperA. Một cách mới để thực hiện đánh giá câu lệnh mà không có nhiều câu lệnh if kể từ c # 7 là sử dụng câu lệnh Switch đối sánh mẫu, tương tự như cách @STLDeveloper mặc dù cách này đang bật biến được chuyển

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

Tạp chí studio trực quan có một bài viết hay về các khối hộp ghép mẫu có thể đáng xem.


Cảm ơn bạn đã chỉ ra chức năng bổ sung của switchcâu lệnh mới .
STLDev

5
+1 - đây phải là câu trả lời được chấp nhận cho sự phát triển hiện đại (C # 7 trở đi). Một thay đổi mà tôi sẽ thực hiện là tôi viết mã như thế này: case var name when "Bungalow".Equals(name, StringComparison.InvariantCultureIgnoreCase):vì điều này có thể ngăn ngoại lệ tham chiếu null (trong đó houseName là null) hoặc cách khác là thêm một trường hợp cho chuỗi là null trước.
Jay

19

Một cách khả thi là sử dụng từ điển trường hợp bỏ qua với một đại diện hành động.

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

dic["HouSe"]();

// Lưu ý rằng lệnh gọi không trả về văn bản mà chỉ điền biến cục bộ s.
// Nếu bạn muốn trả về văn bản thực, hãy thay thế Actionthành Func<string>và các giá trị trong từ điển thành một cái gì đó như() => "window2"


4
Hơn là CurrentCultureIgnoreCase, OrdinalIgnoreCaseđược ưu tiên.
Richard Ev

2
@richardEverett Được ưa thích? Phụ thuộc vào những gì bạn muốn, nếu bạn muốn văn hóa hiện tại bỏ qua trường hợp nó không được ưu tiên.
Magnus

Nếu bất kỳ ai quan tâm, giải pháp của tôi (bên dưới) lấy ý tưởng này và kết thúc nó trong một lớp đơn giản.
Flydog57

2

Đây là giải pháp kết hợp giải pháp của @Magnus trong một lớp:

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

Dưới đây là một ví dụ về việc sử dụng nó trong một ứng dụng Windows Form đơn giản:

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

Nếu bạn sử dụng lambdas (như ví dụ), bạn sẽ nhận được các bao đóng sẽ nắm bắt các biến cục bộ của bạn (khá gần với cảm giác bạn nhận được từ một câu lệnh switch).

Vì nó sử dụng một Từ điển dưới vỏ bọc, nó có hành vi O (1) và không dựa vào việc xem qua danh sách các chuỗi. Tất nhiên, bạn cần phải xây dựng từ điển đó, và điều đó có thể tốn nhiều tiền hơn.

Có lẽ sẽ có ý nghĩa nếu thêm một bool ContainsCase(string aCase)phương thức đơn giản gọi ContainsKeyphương thức của từ điển .


1

Tôi hy vọng điều này sẽ giúp cố gắng chuyển đổi toàn bộ chuỗi thành trường hợp cụ thể hoặc chữ thường hoặc chữ hoa và sử dụng chuỗi chữ thường để so sánh:

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

Cần đủ để làm điều này:

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

Sự so sánh chuyển đổi do đó văn hóa bất biến. Theo như tôi có thể thấy, điều này sẽ đạt được kết quả tương tự như các giải pháp Đối sánh mẫu C # 7, nhưng ngắn gọn hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.