3

リヒターとこの議論から、2 つの「同一の」文字列は同じ参照であることが予想されます。しかし、今LINQPadで、このトピックについてさまざまな結果が得られました。コードは次のとおりです。

void Main()
{
    string alpha = String.Format("Hello{0}", 5);
    string brava = String.Format("Hello{0}", 5);
    ReferenceEquals(alpha, brava).Dump();
    String.IsInterned(alpha).Dump();
    String.IsInterned(brava).Dump();

    alpha = "hello";
    brava = "hello";
    ReferenceEquals(alpha, brava).Dump();
}

Dump() 呼び出しの結果は次のとおりです。

False
Hello5
Hello5
True

最初と最後の両方ReferenceEqualsTrue. どうしたの?

上記の例以外に、ReferenceEquals が失敗するのはどのような場合ですか? たとえば、マルチスレッド?

この問題は、たとえば、メソッドに渡された文字列パラメーターをロックが取得されるオブジェクトとして使用している場合に重要です。その場合、参照は同じである方がよい!!!

4

5 に答える 5

3

文字列のインターンは、発生することが保証されていません。これは決して信頼されるべきではありません。

最後の比較でが得られTrueます。これは、「カジュアルな」インターンが発生したためではなく、両方の文字列が同一の文字列リテラル "hello"から初期化されたためです。その特定のケースでは、彼らは抑留されているでしょう。これは、Svickによるリンクされた回答で説明されました。

また、実際に行う必要はありません。

String.Equals文字列を比較するために使用します。

ロックに関する質問の更新

別のロック変数が必要です。通常のパターンには

private /*readonly*/ object lockObject = new object();

オブジェクト(この場合は文字列)を含むスコープ内で、保護することになっています。これは、参照が変更された場合に堅牢に機能できる唯一の方法です。

于 2012-10-10T22:56:05.870 に答える
3

文字列のインターンは、動的に作成された文字列では発生しません。これには、String.FormatおよびStringBuilderによって作成されたものが含まれます(String.Formatは内部でStringBuilderを使用すると思います)。String.Internに関するMSDNのドキュメントには、次のことが示されています。

次の例では、「MyTest」の値を持つ文字列s1は、プログラム内のリテラルであるため、すでにインターンされています。System.Text.StringBuilderクラスは、s1と同じ値を持つ新しい文字列オブジェクトを生成します。その文字列への参照がs2に割り当てられます。Internメソッドは、s2と同じ値を持つ文字列を検索します。このような文字列が存在するため、メソッドはs1に割り当てられているのと同じ参照を返します。次に、その参照がs3に割り当てられます。参照s1とs2は、異なるオブジェクトを参照しているため、等しくありません。参照s1とs3は、同じ文字列を参照しているため、同等に比較されます。

string s1 = "MyTest";
string s2 = new StringBuilder().Append("My").Append("Test").ToString();  
string s3 = String.Intern(s2);  
Console.WriteLine((Object)s2==(Object)s1); //Different references. 
Console.WriteLine((Object)s3==(Object)s1); //The same reference.

注意すべき重要な点は、CLRにとって、によって生成されstring.Format("Hello{0}", 5)た文字列はリテラル文字列として表示されないため、アセンブリが読み込まれるときにインターンが発生しないことです。一方、文字列"hello"はCLRによってインターンされます。これらの文字列をインターンするには、String.Internを使用して明示的にインターンする必要があります。

編集

ロックの質問に関しては、理論的には文字列をロックオブジェクトとして使用できますが、これは悪い習慣だと思います。アプリケーションに渡された文字列がどこから来たのかわからないため、それらが同じ参照であるという保証はありません。文字列は、StringBuilderを使用した、String.Formatを使用した、またはユーザー入力を使用したデータベース読み取り呼び出しから取得された可能性があります。このような場合、文字列のインターンが発生することが保証されていないため、ロックによって一度にクリティカルセクションにスレッドが1つだけ存在することは保証されません。

常にインターン文字列を使用していることを保証できたとしても、潜在的に危険な問題が発生する可能性があります。これで、誰でも同じ文字列参照をアプリケーション(他のAppDomainを含む)のどこにでもロックできます。これは悪いニュースです。

明示的に宣言されたロックオブジェクト(オブジェクト型)を用意することをお勧めします。スレッドの問題が発生した場合のデバッグにかかる​​時間を大幅に節約できます。

于 2012-10-10T23:25:53.750 に答える
2

元の質問に答えるために、文字列のインターンは定数文字列のみを対象とし、明示的に要求した場合、少なくともその答えに従ってください。

ただし、特定の文字列がインターンされていることを保証したい場合は、 を呼び出すことができますstring.Intern

string internedVersion = string.Intern("Some string");

対照的に、存在する場合string.IsInternedはインターンされた文字列を返します。またはを呼び出して、これらのメソッドのいずれかの戻り値を使用していない限り、特定のオブジェクトに対してそのインターンされた文字列があるという保証はありません。stringInternIsInterned

この問題は、たとえば、メソッドに渡された文字列パラメーターをロックが取得されるオブジェクトとして使用している場合に重要です。その場合、参照は同じである方がよい!!!

文字列オブジェクトをロックするべきではありません。それらの文字列をロックしているものは (interning のため) わかりません。

文字列をロックする必要がある場合は、代わりに次のことをお勧めします。

Dictionary<string,object> locks;
locks.Add("TEST", new object());
lock (locks["TEST"])
{
}

文字列によって提供されるデフォルトの等価性のおかげで、このロジックはインターンされていない文字列でも機能することに注意してください。

あるいは、独自のクラスを作成して文字列をラップし、等価性を処理することもできますが、それはおそらくロックにはやり過ぎです。

于 2012-10-10T23:01:49.763 に答える
2

このブログエントリはその理由を説明しています。

つまり、文字列が割り当てられていない場合ldstr(つまり、コード内で定義された文字列リテラルではない場合)、インターンされた文字列の (ハッシュ) テーブルに収まらないため、インターンは発生しません。

解決策は、 を呼び出すことString.Intern(str)です。Intern メソッドは、インターン プールを使用して、 の値に等しい文字列を検索しますstr。そのような文字列が存在する場合は、インターン プール内のその参照が返されます。文字列が存在しない場合は、str への参照がインターン プールに追加され、その参照が返されます。

特に、2 つの異なる参照変数を使用して同じ (おそらく) インターンされた文字列を指そうとする場合は、文字列をロックしないでください。

また、文字列をインターンすることにはいくつかの欠点があることにも注意してください。プログラムの存続期間中に文字列リテラルが変更されることは想定されていないため、インターンされた文字列は、プログラムが終了するまでガベージ コレクションされません。

于 2012-10-10T23:18:58.673 に答える
1

2 番目のケースをより興味深いものにするには:

alpha = "hello";
brava = "hell" + "o";
ReferenceEquals(alpha, brava).Dump();

実装は非常に簡単です。特定の文字列が文字列の別のインスタンスと一致することを誰かが認識する努力をしなければなりません。必然的に時間がかかります。実行時の時間が不足しているため、文字列処理は高速である必要があります。しかし、コンパイラは一致を見つけるのが得意で、文字列リテラルを使用してハッシュ テーブルを作成できます。したがって、基本的なルールは、コンパイル時の定数文字列式のみがインターンされるということです。

于 2012-10-11T00:17:44.770 に答える