switch内のオブジェクトがstringであるswitch-caseステートメントがある場合、ignoreCase比較を実行することは可能ですか?
私は例えば持っています:
string s = "house";
switch (s)
{
case "houSe": s = "window";
}
ウィルs
値「ウィンドウ」を取得?ignoreCaseを使用して文字列を比較するように、switch-caseステートメントをオーバーライドするにはどうすればよいですか?
回答:
ご存知のように、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";
}
ToLower()
またはのいずれかを使用した(switchステートメントのような)順序の同等性ToLowerInvariant()
はfalseを返します。Equals
withStringComparison.InvariantCultureIgnoreCase
はtrueを返します。両方のシーケンスを表示すると同じように見えるため、このToLower()
バージョンは追跡するのが難しいバグです。これが、トルコ語でなくても、適切な文字列比較を行うことが常に最善である理由です。
より簡単なアプローチは、文字列がswitchステートメントに入る前に文字列を小文字にし、大文字と小文字を区別することです。
実際、アッパーは純粋に極端なナノ秒のパフォーマンスの観点からは少し優れていますが、見るのは自然ではありません。
例えば:
string s = "house";
switch (s.ToLower()) {
case "house":
s = "window";
break;
}
ToUpper(Invariant)
:だけでなく、より速く、より信頼性のあるstackoverflow.com/a/2801521/67824
古い質問に対するこの新しい投稿については申し訳ありませんが、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
は未使用であり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;
}
switch (houseName)
、あなたの方法と同じように、それをやった比較を行う、つまりcase var name when name.Equals("MyHouse", ...
switch
への引数値の割り当てに関する議論をさらに追加しましたcase
。
case { } when
、変数の型や名前を気にする必要はありません。
場合によっては、列挙型を使用することをお勧めします。したがって、最初に列挙型を解析し(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;
}
}
@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
ステートメントの追加機能を指摘していただきありがとうございます。
case var name when "Bungalow".Equals(name, StringComparison.InvariantCultureIgnoreCase):
これにより、null参照例外(houseNameがnullの場合)を防ぐことができます。または、最初に文字列がnullの場合を追加することもできます。
考えられる1つの方法は、アクションデリゲートでケース無視ディクショナリを使用することです。
string s = null;
var dic = new Dictionary<string, Action>(StringComparer.CurrentCultureIgnoreCase)
{
{"house", () => s = "window"},
{"house2", () => s = "window2"}
};
dic["HouSe"]();
//呼び出しはテキストを返さず、ローカル変数にのみデータを入力することに注意してください。
//あなたが実際のテキストを返すようにしたい場合は、交換してくださいAction
にFunc<string>
し、辞書内の値のようなものに() => "window2"
CurrentCultureIgnoreCase
、OrdinalIgnoreCase
が優先されます。
@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
メソッドを呼び出すだけの単純なメソッドを追加することはおそらく理にかなっています。
これが文字列全体を小文字または大文字の特定の大文字に変換し、比較のために小文字の文字列を使用するのに役立つことを願っています。
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();
}
}
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
};
ToUpperInvariant()
かToLowerInvariant()
?また、彼は2つの未知の文字列を比較しているのではなく、1つの未知の文字列を1つの既知の文字列と比較しています。したがって、適切な大文字または小文字の表現をハードコーディングする方法を知っている限り、スイッチブロックは正常に機能するはずです。