C#SwitchステートメントでIgnoreCaseを使用する方法


92

switch内のオブジェクトがstringであるswitch-caseステートメントがある場合、ignoreCase比較を実行することは可能ですか?

私は例えば持っています:

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

ウィルs値「ウィンドウ」を取得?ignoreCaseを使用して文字列を比較するように、switch-caseステートメントをオーバーライドするにはどうすればよいですか?

回答:


64

ご存知のように、2つの文字列を小文字にして比較することは、大文字と小文字を区別しない比較を行うことと同じではありません。これには多くの理由があります。たとえば、Unicode標準では、発音区別符号を含むテキストを複数の方法でエンコードできます。一部の文字には、基本文字と発音区別符号の両方が1つのコードポイントに含まれています。これらの文字は、基本文字の後に合成発音区別符号が続くものとして表すこともできます。これらの2つの表現はすべての目的で同等であり、.NET Frameworkでのカルチャ対応の文字列比較により、CurrentCultureまたはInvariantCulture(IgnoreCaseの有無にかかわらず)のいずれかで、それらが等しいものとして正しく識別されます。一方、序数の比較では、それらは誤って等しくないと見なされます。

残念ながら、switch序数の比較以外は何もしません。厳密に定義されたコードを使用してASCIIファイルを解析するなど、特定の種類のアプリケーションでは序数の比較は問題ありませんが、他のほとんどの用途では序数の文字列の比較は誤りです。

正しい動作を得るために過去に行ったことは、自分のswitchステートメントをモックアップすることです。これを行う方法はたくさんあります。1つの方法はList<T>、ケース文字列とデリゲートのペアを作成することです。リストは、適切な文字列比較を使用して検索できます。一致するものが見つかると、関連する委任が呼び出される場合があります。

別のオプションは、ifステートメントの明白なチェーンを実行することです。構造が非常に規則的であるため、これは通常、思ったほど悪くはないことがわかります。

これの素晴らしい点は、文字列と比較するときに、独自のスイッチ機能をモックアップしてもパフォーマンスが低下しないことです。システムは、整数の場合のようにO(1)ジャンプテーブルを作成しないため、とにかく各文字列を一度に1つずつ比較します。

比較するケースが多く、パフォーマンスが問題になるList<T>場合は、上記のオプションを並べ替えられたディクショナリまたはハッシュテーブルに置き換えることができます。その場合、パフォーマンスは、switchステートメントオプションと一致するか、それを超える可能性があります。

代理人のリストの例を次に示します。

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
私が間違えない限り、この2つは特定の文化(トルコ語など)でのみ異なります。その場合、彼は使用できませんでしたToUpperInvariant()ToLowerInvariant()?また、彼は2つの未知の文字列を比較しているのではなく、1つの未知の文字列を1つの既知の文字列と比較しています。したがって、適切な大文字または小文字の表現をハードコーディングする方法を知っている限り、スイッチブロックは正常に機能するはずです。
Seth Petry-Johnson 2010

8
@Seth Petry-Johnson-おそらく最適化を行うことができますが、文字列比較オプションがフレームワークに組み込まれている理由は、正確で拡張可能なソフトウェアを作成するために言語学の専門家になる必要がないためです。
Jeffrey L Whitledge 2010

59
OK。これが信頼できる例を示します。「家」の代わりに(英語!)「カフェ」という単語があったとします。この値は、「caf \ u00E9」または「cafe \ u0301」のいずれかで同じようにうまく(そして同じように可能性が高い)表すことができます。ToLower()またはのいずれかを使用した(switchステートメントのような)順序の同等性ToLowerInvariant()はfalseを返します。EqualswithStringComparison.InvariantCultureIgnoreCaseはtrueを返します。両方のシーケンスを表示すると同じように見えるため、このToLower()バージョンは追跡するのが難しいバグです。これが、トルコ語でなくても、適切な文字列比較を行うことが常に最善である理由です。
ジェフリーLホイットレッジ2010

78

より簡単なアプローチは、文字列がswitchステートメントに入る前に文字列を小文字にし、大文字と小文字を区別することです。

実際、アッパーは純粋に極端なナノ秒のパフォーマンスの観点からは少し優れていますが、見るのは自然ではありません。

例えば:

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

1
はい、小文字が方法であることは理解していますが、それからignoreCaseにしたいと思います。switch-caseステートメントをオーバーライドする方法はありますか?
トルサン2010

6
@ Lazarus-これはCLRからC#経由で、しばらく前に非表示の機能スレッドにも投稿されました:stackoverflow.com/questions/9033/hidden-features-of-c/…LinqPadを 起動することができます百万回の繰り返しが当てはまります。
NickCraver

1
@ Tolsan-いいえ、残念ながら、静的な性質のためだけではありません。しばらく前に、これについてはたくさんの答えがありました:stackoverflow.com/questions/44905/…–
NickCraver

9
それが表示されますToUpper(Invariant):だけでなく、より速く、より信頼性のあるstackoverflow.com/a/2801521/67824
オハッドシュナイダー


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による回答で言及されている、大文字と小文字を区別しない文字列の比較は2つの小文字の文字列の比較と同じではないという問題にも対処します。

ちなみに、2017年2月にVisual Studio Magazineに、パターンマッチングと、ケースブロックでの使用方法について説明した興味深い記事がありました。ご覧ください:C#7.0ケースブロックでのパターンマッチング

編集

@LewisMの回答に照らして、switchステートメントにはいくつかの新しい興味深い動作があることを指摘することが重要です。つまり、caseステートメントに変数宣言が含まれている場合、そのswitch部分で指定された値は、で宣言された変数にコピーされますcase。次の例では、値trueがローカル変数にコピーされますb。さらに、変数bは未使用でありwhencaseステートメントの句が存在できるようにするためにのみ存在します。

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 2018年

@ LewisM-すばらしい答えです。一時変数switchへの引数値の割り当てに関する議論をさらに追加しましたcase
STLDev 2018年

現代のC#でのパターンマッチングのためのイェーイ
チアゴ・シウバ

このように「オブジェクトパターンマッチング」を使用することもできるのでcase { } when、変数の型や名前を気にする必要はありません。
ボブ

32

場合によっては、列挙型を使用することをお勧めします。したがって、最初に列挙型を解析し(ignoreCaseフラグをtrueにして)、次に列挙型をオンにします。

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

注:列挙型TryParseは、Framework4.0以降で使用できるようです。 msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx
granadaCoder 2013年

4
マジックストリングの使用を思いとどまらせるので、私はこの解決策を好みます。
user1069816 2014

23

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

Visual Studioマガジンには、一見の価値があるかもしれないパターンマッチングケースブロックに関する素晴らしい記事があります。


新しいswitchステートメントの追加機能を指摘していただきありがとうございます。
STLDev 2018年

5
+ 1-これは、現代(C#7以降)の開発で受け入れられている答えです。私が行う変更の1つは、次のようにコーディングすることです。case var name when "Bungalow".Equals(name, StringComparison.InvariantCultureIgnoreCase):これにより、null参照例外(houseNameがnullの場合)を防ぐことができます。または、最初に文字列がnullの場合を追加することもできます。
ジェイ

21

考えられる1つの方法は、アクションデリゲートでケース無視ディクショナリを使用することです。

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

dic["HouSe"]();

//呼び出しはテキストを返さず、ローカル変数にのみデータを入力することに注意してください。
//あなたが実際のテキストを返すようにしたい場合は、交換してくださいActionFunc<string>し、辞書内の値のようなものに() => "window2"


4
よりもCurrentCultureIgnoreCaseOrdinalIgnoreCaseが優先されます。
Richard Ev 2018

2
@richardEverett優先?必要なものによって異なりますが、現在のカルチャを無視する場合はお勧めしません。
マグナス

誰かが興味を持っているなら、私の解決策(以下)はこのアイデアを取り入れて、それを単純なクラスにラップします。
flydog 5719

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フォームのアプリで使用する例を次に示します。

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

(例のように)ラムダを使用すると、ローカル変数をキャプチャするクロージャが得られます(switchステートメントから得られる感覚にかなり近い)。

裏で辞書を使用するため、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.