String型の等価比較

タグの編集
投稿者 ユニ  (社会人) 投稿日時 2009/11/4 07:44:56
String型の挙動について勉強しているのですが等価比較で「なぜ?」と思っていることがあります。

例1
Dim x As String = "1"
Dim y As String = "1"
If x is y Then
  MsgBox("等価")
End If

例2
Dim x As String = 1
Dim y As String = "1"
If x is y Then
  MsgBox("等価")
End If

上記の例ではis演算子を使用して参照の等価を調べています。
結果は例1は"等価"とメッセージが表示されます。
例2はメッセージは表示されません。
ここで疑問があります。is演算子は参照の等価を調べるはずです。
なのに例1では値の等価を調べているように思われます。
例2に関してはなぜこのような挙動をとるのか検討もつきません。
値の等価を調べているならメッセージが表示されるはずです。
なのに""をはずすと等価ではないということなのでしょうか。
ちなみにEqualsメソッドで同様のことを試しましたが
結果は例1、例2ともにメッセージが表示されました。
この結果はStringクラスがEqualsメソッドをオーバーライドして
おり、値の等価を調べていると理解できます。

例1、例2に関して皆さんの意見をお聞きしたいです。
宜しくお願いします。m(_ _)m
投稿者 daive  (社会人) 投稿日時 2009/11/4 08:43:41
その様な投稿をする場合は、した結果を、うんぬんする前に、、
言語の名称、バージョン、種類などと、
OPTION設定、コンパイル設定などを、記述しないと他の人が検証できないかと。
私は、VB2005/2008Professionalの通常設定を、
Option Compare Binary
Option Explicit On
Option Strict On
としています。
(記述していない場合は、オプションの、プロジェクトおよびソリューション辺りに、
Visual Basicの規定値があります。)
ですので、
>Dim x As String = 1
Option Strict On で、'Integer' から 'String' への暗黙の変換はできません。
という、IDEレベルのエラーになります。
投稿者 (削除されました)  () 投稿日時 2009/11/4 08:50:55
(削除されました)
投稿者 (削除されました)  () 投稿日時 2009/11/4 20:24:06
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/4 20:27:04
> is演算子は参照の等価を調べるはずです。
その通りです。ReferenceEquals メソッドの動作ですね。


> なのに例1では値の等価を調べているように思われます。
ここには、String 型であるが故の注意点があります。


まず、String 型とは値が固定的であり、通常、その値は変更できないことになっています。例えば、
  Dim x As String = "abc"
  x = "def"
という処理は、一つの文字列インスタンスの値を abc → def に書き換えているのではなく、
abc というインスタンスを捨てて、def という別のインスタンスを割り当てる事を意味します。

この「内容が変化しない」という特性ゆえ、これらのインスタンスが同一であろうとなかろうと、
利用側には影響が少ないため、まるで値型と同じように振る舞えるようになっています。この特性を活かし
リテラル文字列については、同じ文字列参照を流用することでメモリ使用量を抑えるようになっています。
(この仕組みを、文字列インターン プールと言います)
http://msdn.microsoft.com/ja-jp/library/system.string.intern.aspx
http://msdn.microsoft.com/ja-jp/library/system.reflection.emit.opcodes.ldstr.aspx


ただし、同じ内容が常に同一インスタンスになるわけではありません。今回のようなパターンや
「New String( Char配列 )」で生成された場合などでは、それぞれユニークなインスタンスを返します。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/5 01:11:15
> この結果はStringクラスがEqualsメソッドをオーバーライドして
> おり、値の等価を調べていると理解できます。

そうですね。実際ヘルプ等にも、値比較のためにオーバーライドされていると記されています。
http://msdn.microsoft.com/ja-jp/library/system.string.equals.aspx


御存知かと思いますが、Equals の既定の実装が「参照型では参照の等価」「値型ではバイナリの一致性」
というだけであって、派生型においてはそれらがオーバーライドされる事があります。

たとえば、『値型』である Decimal 型では、1.10 と 1.1000 を別の内部形式で管理していますが、
それぞれを同じ値として扱えるよう、Equals がオーバーライドされています。
Dim a As Decimal = Decimal.Parse("1.10")
Dim b As Decimal = Decimal.Parse("1.1000")

Console.WriteLine("A={0}", a)                                      'A=1.10 
Console.WriteLine("B={0}", b)                                      'B=1.1000 
Console.WriteLine("ReferenceEquals : {0}", ReferenceEquals(a, b))  'ReferenceEquals : False 
Console.WriteLine("Equals : {0}", Equals(a, b))                    'Equals : True 
Console.WriteLine("= : {0}", a = b)                                '= : True 



また、『参照型』である IPAddress 型でも、アドレスが同じであれば、異なるインスタンスでも
同じ値として扱えるよう、Equals メソッドがオーバーライドされています。
Dim a As IPAddress = IPAddress.Parse("143.24.20.36")
Dim b As New IPAddress(New Byte() {143, 24, 20, 36})

Console.WriteLine("ReferenceEquals : {0}", ReferenceEquals(a, b))  'ReferenceEquals : False 
Console.WriteLine("Is 演算子 : {0}", a Is b)                       'Is 演算子 : False 
Console.WriteLine("Equals : {0}", Equals(a, b))                    'Equals : True 




> 皆さんの意見をお聞きしたいです。

特に意見と呼べるほどの物は持ち合わせていませんが、今回の件の関連情報ということで:
VB の「= 比較演算子」が、String.Equals とは異なる実装になっているという事も
蛇足程度には知っておくと良いかも知れません。
投稿者 聖帝  (中学生) 投稿日時 2009/11/5 05:26:43
カタは面倒くさいね。
strict onとかさ。
コレ使うとCint入れないとエラーになるしさ。^^;
他の変換も面倒くさいですね。
教科書もOnの人とoffの人がいるから、、。。
まぁ、沢山の本を読むと理解が深まりますが...^^;
投稿者 ユニ  (社会人) 投稿日時 2009/11/5 08:08:51
>daiveさん
すいません。投稿するのは初めてで以後気をつけますm(_ _)m
私が試したのはVB2008のExpress Editionです。
Frameworkは3.5です。 
コンパイル設定等は全て既定値です。
テストのためにあえて暗黙の型変換は規制せず行いました。

>魔界の仮面弁士さん
文字列インターンプールですか。私も色々調べていたのですがそこまで
調べきれませんでした。ありがとうございます。

前からStringクラスは特殊だと知っていましたがここまで奥が深いと
驚かされます。

例2のような特殊なケースを探してみましたが、他に
""とString.Empty(Framework3.5だと等価になるがFramework2.0
だと等価にならない)があります。
どうして等価にならないのか仕組みを調べてみましたが、変数にリテラル文字列
を代入すると文字列インターンプールへの登録が行われるそうです。
リテラル文字列を代入しないやり方(今回のint型をString型へ代入、若しくは
New String( Char配列 )、String.Empty)はインターンプールへの登録は行われず、
結果、is演算子はFalseを返却したということなのでしょう。

参照型で比較演算子を使用して値の等価を調べられるクラスは比較演算子を
オーバーロードしているということですね。^^;

>聖帝さん
私は型変換は結構シビアに考えていますが、でも参照型の型変換とか
面倒ですね。縮小とか拡大とか考えなくてはいけないので。^^;