web-dev-qa-db-ja.com

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

スイッチ内のオブジェクトが文字列であるswitch-caseステートメントがある場合、とにかくignoreCase比較を行うことは可能ですか?

私は例えば:

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

値「window」を取得します。 switchCaseステートメントをオーバーライドして、ignoreCaseを使用して文字列を比較する方法

68
Tolsan

ご存知のように、2つの文字列を小文字にして比較することは、大文字と小文字を区別しない比較を行うことと同じではありません。これには多くの理由があります。たとえば、Unicode標準では、発音区別符号付きのテキストを複数の方法でエンコードできます。一部の文字には、単一のコードポイントに基本文字と発音区別符号の両方が含まれています。これらの文字は、基本文字の後に分音記号文字を組み合わせて表すこともできます。これら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";
    }
56

より単純な方法は、switchステートメントに入る前に文字列を小文字化し、ケースを低くすることです。

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

例えば。:

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

場合によっては、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;
   }
}
29
uli78

古い質問に対するこの新しい投稿で申し訳ありませんが、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つの小文字の文字列の比較と同じではないという問題も扱います。

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

[〜#〜] edit [〜#〜]

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

可能な方法の1つは、アクションデリゲートで大文字と小文字を区別しない辞書を使用することです。

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

dic["HouSe"]();
16
Magnus

@STLDeveloperAによる回答の拡張。 @#

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

ビジュアルスタジオマガジンには、 パターンブロックマッチングケースブロックに関する素敵な記事 があり、一見の価値があります。

12
LewisM

@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)動作を取得し、文字列のリストをたどることに依存しません。もちろん、その辞書を作成する必要があります。おそらくもっと費用がかかります。

辞書のContainsKeyメソッドを単純に呼び出す単純なbool ContainsCase(string aCase)メソッドを追加することはおそらく意味があります。

2
Flydog57

これが文字列全体を小文字または大文字の特定のケースに変換し、比較のために小文字の文字列を使用しようとすることを願っています:

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