C # Switch 문에서 IgnoreCase를 사용하는 방법


89

스위치의 객체가 문자열 인 switch-case 문이있는 경우 ignoreCase 비교를 수행 할 수 있습니까?

예를 들면 다음과 같습니다.

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

s값 "창"을 얻을? ignoreCase를 사용하여 문자열을 비교하도록 switch-case 문을 어떻게 재정의합니까?

답변:


63

아시다시피 두 문자열을 소문자로 비교하는 것은 대소 문자를 무시하는 비교와 동일하지 않습니다. 이에 대한 많은 이유가 있습니다. 예를 들어 유니 코드 표준에서는 분음 부호가있는 텍스트를 여러 방법으로 인코딩 할 수 있습니다. 일부 문자에는 단일 코드 포인트에 기본 문자와 분음 부호가 모두 포함됩니다. 이러한 문자는 결합 분음 부호 문자가 뒤 따르는 기본 문자로 표시 될 수도 있습니다. 이 두 표현은 모든 목적에서 동일하며 .NET Framework의 문화 인식 문자열 비교는 CurrentCulture 또는 InvariantCulture (IgnoreCase 포함 또는 제외)를 사용하여 동일한 것으로 올바르게 식별합니다. 반면에 서수 비교는 그것들을 동일하지 않은 것으로 잘못 간주합니다.

불행히도 switch서수 비교 만 수행합니다. 서수 비교는 엄격하게 정의 된 코드로 ASCII 파일을 구문 분석하는 것과 같은 특정 종류의 응용 프로그램에 적합하지만 대부분의 다른 용도에서는 서수 문자열 비교가 잘못되었습니다.

올바른 동작을 얻기 위해 과거에 한 일은 내 자신의 switch 문을 모의하는 것입니다. 이를 수행하는 방법에는 여러 가지가 있습니다. 한 가지 방법은 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 대리자에 몇 가지 표준 매개 변수와 반환 형식을 추가하고 싶을 것입니다. 그리고 당신은 더 나은 이름을 만들고 싶을 것입니다!

다른 매개 변수가 필요한 경우와 같이 각 사례의 동작이 이러한 방식으로 호출을 위임 할 수없는 경우 연결된 상태가 if됩니다. 나는 또한 이것을 몇 번했다.

    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

54
확인. 이것이 의존하는 예를 들겠습니다. "집"대신에 (영어!) 단어 "카페"가 있다고 가정합니다. 이 값은 "caf \ u00E9"또는 "cafe \ u0301"로 동일하게 잘 표현 될 수 있습니다. ToLower()or ToLowerInvariant()가 있는 서수 같음 (switch 문에서와 같이) 은 false를 반환합니다. Equals와 함께 StringComparison.InvariantCultureIgnoreCasetrue를 반환합니다. 두 시퀀스가 ​​모두 표시 될 때 동일하게 보이기 때문에 ToLower()버전은 추적하기 어려운 버그입니다. 이것이 터키어가 아니더라도 항상 적절한 문자열 비교를 수행하는 것이 가장 좋은 이유입니다.
Jeffrey L Whitledge

77

더 간단한 방법은 switch 문으로 들어가기 전에 문자열을 소문자로하고 case를 낮추는 것입니다.

실제로, 어퍼는 순수한 극한의 나노초 성능 관점에서 약간 더 좋지만보기에는 덜 자연 스럽습니다.

예 :

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

1
예, 소문자로 처리하는 것이 방법이라는 것을 이해하지만, 그로부터 ignoreCase가되기를 원합니다. switch-case 문을 재정의 할 수있는 방법이 있습니까?
Tolsan

6
@Lazarus-이것은 C #을 통해 CLR에서 가져온 것입니다. 잠시 동안 숨겨진 기능 스레드에도 게시되었습니다. 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
오핫 슈나이더


47

이전 질문에 대한이 새 게시물에 대해 죄송하지만 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 년 2 월 Visual Studio Magazine에서 패턴 매칭과 케이스 블록에서 어떻게 사용할 수 있는지에 대해 설명하는 흥미로운 기사가있었습니다. 보세요 : C # 7.0 케이스 블록의 패턴 일치

편집하다

@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-흥미 롭군요. 작동하는 예를 보여줄 수 있습니까?
STLDev

@LewisM-좋은 대답입니다. 임시 변수에 대한 switch인수 값 할당에 대한 추가 논의를 추가했습니다 case.
STLDev

현대 C #의 패턴 매칭을위한 야호
티아고 실바

이와 같이 "객체 패턴 일치"를 사용할 수도 case { } when있으므로 변수 유형과 이름에 대해 걱정할 필요가 없습니다.
Bob

32

어떤 경우에는 열거 형을 사용하는 것이 좋습니다. 따라서 먼저 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 2013 년

4
이 솔루션은 매직 스트링 사용을 권장하지 않기 때문에 선호합니다.
user1069816

21

@STLDeveloperA의 답변에 대한 확장입니다. C # 7부터 여러 if 문없이 문 평가를 수행하는 새로운 방법은이 방식으로 전환되는 변수를 전환하지만 @STLDeveloper와 유사한 방식으로 패턴 일치 Switch 문을 사용하는 것입니다.

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):null 참조 예외 (houseName이 null 인 경우)를 방지 할 수 있거나 또는 문자열이 null 인 경우를 먼저 추가 할 수 있기 때문입니다.
Jay

19

한 가지 가능한 방법은 액션 델리게이트와 함께 케이스 무시 사전을 사용하는 것입니다.

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

dic["HouSe"]();

// 호출은 텍스트를 반환하지 않고 지역 변수 만 채 웁니다.
// 당신은 실제 텍스트를 반환 교체 할 경우 ActionFunc<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"]();

예와 같이 람다를 사용하면 지역 변수를 캡처하는 클로저를 얻을 수 있습니다 (switch 문에서 얻는 느낌과 거의 비슷합니다).

내부적으로 Dictionary를 사용하기 때문에 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 패턴 일치 솔루션과 동일한 결과를 얻을 수 있지만 더 간결합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.