C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[6]: クラスで定義した変数を比較する方法


(過去ログ 141 を表示中)

[トピック内 9 記事 (1 - 9 表示)]  << 0 >>

■82660 / inTopicNo.1)  クラスで定義した変数を比較する方法
  
□投稿者/ スキン (1回)-(2017/01/25(Wed) 09:23:53)

分類:[.NET 全般] 



Public Class test

Property aaa As String
Property bbb As String
Property ccc As String

End Class

Sub

Dim xxx as test

xxx.aaa="あああ"
xxx.bbb="いいい"
xxx.ccc="ううう"


Dim yyy as test

yyy.aaa="あああ"
yyy.bbb="いいい"
yyy.ccc="えええ"

End Sub


このコードで
変数xxxと変数yyyが完全に等しいか調べたいのですが


If xxx.aaa = yyy.aaa And xxx.bbb = yyy.bbb And xxx.ccc = yyy.ccc then


とやるのは面倒なので


If xxx = yyy then

とやりたいのですが
うまくいきません。
If xxx is yyy then
でも駄目でした

クラスで定義した変数をまるごと比較できる方法があれば教えてください。










引用返信 編集キー/
■82661 / inTopicNo.2)  Re[1]: クラスで定義した変数を比較する方法
□投稿者/ WebSurfer (1127回)-(2017/01/25(Wed) 10:07:42)
No82660 (スキン さん) に返信

> クラスで定義した変数をまるごと比較できる方法があれば教えてください。

そのクラスに比較のためのメソッドを定義・追加したらいかがですか?
引用返信 編集キー/
■82662 / inTopicNo.3)  Re[1]: クラスで定義した変数を比較する方法
□投稿者/ 魔界の仮面弁士 (1075回)-(2017/01/25(Wed) 10:17:21)
2017/01/25(Wed) 10:50:34 編集(投稿者)

No82660 (スキン さん) に返信
> Dim xxx as test
> xxx.aaa="あああ"
> xxx.bbb="いいい"
> xxx.ccc="ううう"

test が Structure であれば、そのようにも書けますが、
今回は Class なので、使用前に New test() を呼び出さないと
NullRefrenceException になってしまいますよ。


> 変数xxxと変数yyyが完全に等しいか調べたいのですが

等価演算子を実装するには、Clsss test 内に

 Public Shared Operator =(ByVal l As test, ByVal r As test) As Boolean
  If l Is Nothing Then
   Return r Is Nothing
  ElseIf r Is Nothing Then
   Return False
  Else
   'VB での文字列比較は「Option Compare」依存であることに注意
   Return l.aaa = r.aaa AndAlso l.bbb = r.bbb AndAlso l.ccc = r.ccc
  End If
 End Operator
 Public Shared Operator <>(ByVal l As test, ByVal r As test) As Boolean
  Return Not (l = r)
 End Operator
 Public Overrides Function GetHashCode() As Integer
  Dim hash As Integer = MyBase.GetHashCode()
  If aaa IsNot Nothing Then hash = hash Xor aaa.GetHashCode()
  If bbb IsNot Nothing Then hash = hash Xor bbb.GetHashCode()
  If ccc IsNot Nothing Then hash = hash Xor ccc.GetHashCode()
  Return hash
 End Function

のように仕込めば OK です。

これにより、呼び出し側が『If xxx = yyy Then』だけで済みます。
http://smdn.jp/programming/netfx/comparison/1_equivalence_operator_overload/


※上記実装は一例です。たとえば「If xxx = Nothing Then」の判定を
 True / False のいずれと看做すかなどの調整は、適宜行ってください。



> If xxx = yyy then
> If xxx is yyy then

Is/IsNot は同一参照かどうかの判定ですね。

Imports System.Net
Module Module1
 Sub Main()
  Dim a, b, c As IPAddress

  'クラスなので、インスタンス化が必要
  a = New IPAddress(New Byte() {192, 0, 2, 1})
  b = IPAddress.Parse("192.0.2.1")
  c = a  '変数 c は、a と同じインスタンスを参照している

  'a、b、c はいずれも同じ内容を表示する
  Console.WriteLine("a : {0}", a)
  Console.WriteLine("b : {0}", b)
  Console.WriteLine("c : {0}", c)

  '『値』として比較すると、a = b = c として扱われる
  Console.WriteLine()
  Console.WriteLine("【a = b】【b = c】【c = a】")
  Console.WriteLine("【{0}】【{1}】【{2}】", a.Equals(b), b.Equals(c), c.Equals(a))

  '『参照』の比較だと、b と c が同一、a は別インスタンス
  Console.WriteLine()
  Console.WriteLine("【a Is b】【b Is c】【c Is a】")
  Console.WriteLine("【{0}】【{1}】【{2}】", a Is b, b Is c, c Is a)
  'Console.WriteLine("【{0}】【{1}】【{2}】", ReferenceEquals(a, b), ReferenceEquals(b, c), ReferenceEquals(c, a))

  Console.Read()
 End Sub
End Module



構造体の場合も、異なるデータを同値とみなす実装にすることがあります。

Dim a As Decimal = CDec("1.2500")
Dim b As Decimal = CDec("1.250")

'a、b は異なる内容
Console.WriteLine("a : {0}", a)
Console.WriteLine("b : {0}", b)

'『値』としては、a = b となる
Console.WriteLine(a = b)
Console.WriteLine(a.Equals(b))
引用返信 編集キー/
■82665 / inTopicNo.4)  Re[2]: クラスで定義した変数を比較する方法
□投稿者/ スキン (3回)-(2017/01/25(Wed) 10:56:32)
ありがとうございます。

コードを理解しようとしているところなのですが

Return l.aaa = r.aaa AndAlso l.bbb = r.bbb AndAlso l.ccc = r.ccc
Return Not (l = r)

のようにReturnなのにIf文のようなものが使われているのを初めて見ましたが
どういうものなのでしょうか?
 
引用返信 編集キー/
■82666 / inTopicNo.5)  Re[3]: クラスで定義した変数を比較する方法
□投稿者/ スキン (4回)-(2017/01/25(Wed) 11:05:06)
あと、

 Public Overrides Function GetHashCode() As Integer
  Dim hash As Integer = MyBase.GetHashCode()
  If aaa IsNot Nothing Then hash = hash Xor aaa.GetHashCode()
  If bbb IsNot Nothing Then hash = hash Xor bbb.GetHashCode()
  If ccc IsNot Nothing Then hash = hash Xor ccc.GetHashCode()
  Return hash
 End Function

というのは必要ですか?

このコードがなくても動作するように思うのですが
 
引用返信 編集キー/
■82667 / inTopicNo.6)  Re[3]: クラスで定義した変数を比較する方法
□投稿者/ 魔界の仮面弁士 (1076回)-(2017/01/25(Wed) 11:11:03)
No82665 (スキン さん) に返信
> Return l.aaa = r.aaa AndAlso l.bbb = r.bbb AndAlso l.ccc = r.ccc
> Return Not (l = r)
> のようにReturnなのにIf文のようなものが使われているのを初めて見ましたが


戻り値が Integer 型のメソッドなら、『Return Integer値を返す式』を使います。
「Return intValue」とか「Return intValue + 1」とか「Return Year(Now)」とか。


今回は、戻り値が Boolean 型のメソッドだったので、
『Return Boolean値を返す式』であるというだけです。

「If l.aaa = r.aaa Then」の l.aaa = r.aaa は、
True または False を返す『式』ですよね。

これが「If (l.aaa = r.aaa) = True Then」なら、
(l.aaa = r.aaa) の部分が先に True/False に評価されてから
「If 評価結果 = True Then」として判定されます。
引用返信 編集キー/
■82668 / inTopicNo.7)  Re[4]: クラスで定義した変数を比較する方法
□投稿者/ 魔界の仮面弁士 (1077回)-(2017/01/25(Wed) 13:26:00)
No82666 (スキン さん) に返信
> Public Overrides Function GetHashCode() As Integer
> というのは必要ですか?

外しても構いません。本来は「=」演算子、「<>」演算子のためではなく、
Equals メソッドと共に用いられるものです。


> このコードがなくても動作するように思うのですが

たとえば HashSet というクラスは「重複しない要素」を管理するコレクションであり、
同じデータを Add しても無視される仕様として設計されています。そのため、

 Dim hs As New HashSet(Of 型)()
 hs.Add( a )
 hs.Add( b )
 MsgBox( hs.Count )

とした場合、a と b が同一であれば 1 件と表示され、
a と b が別物と判断されれば 2 件と表示されることになります。


この場合、コレクションに追加される際に、そのオブジェクトの
GetHashCode() が呼び出されています。

データ型の等しい 2 つのオブジェクトを比較する際、その両者で
GetHashCode が不一致だった場合、両者は異なるデータとして扱われます。

GetHashCode が一致していた場合にのみ、さらに Equals メソッドで
追加判定されます。これにより、毎回 Equals を呼び出すことなく、
高速に同一データの存在を確認できるようになっています。
引用返信 編集キー/
■82669 / inTopicNo.8)  Re[5]: クラスで定義した変数を比較する方法
□投稿者/ 魔界の仮面弁士 (1078回)-(2017/01/25(Wed) 14:51:35)
No82668 (魔界の仮面弁士) に追記
> たとえば HashSet というクラスは「重複しない要素」を管理するコレクションであり、
> 同じデータを Add しても無視される仕様として設計されています。
>
> GetHashCode が一致していた場合にのみ、さらに Equals メソッドで
> 追加判定されます。これにより、毎回 Equals を呼び出すことなく、
> 高速に同一データの存在を確認できるようになっています。

以下、HashSet クラスを交えた具体例です。

この一致性判定は、HashSet クラスの他、List.Contains メソッドの要素チェックや、
Dictionary のキーとしても利用されています。



◆パターンA◆

 'HashSet.Add メソッドを 2 回呼び出しています。
 '
 '1 回目の Add と 2 回目の Add とで同一のインスタンスを
 '指定していますが、Add 前に値を変更しています。
 '
 Dim o As New test()
 Dim hs1 As New HashSet(Of test)
 o.aaa = "1"
 hs1.Add(o)
 o.aaa = "2"
 hs1.Add(o)
 Console.WriteLine(hs1.Count)  '★

 Dim p As New System.Net.IPAddress(New Byte() {192, 0, 2, 1})
 Dim hs2 As New HashSet(Of System.Net.IPAddress)()
 hs2.Add(p)
 p.Address += 1
 hs2.Add(p)
 Console.WriteLine(hs2.Count)


Add された時に、GetHashCode 値が取得され、コレクション内に記録されます。

もし、GetHashCode をオーバーライドしていなかった場合、
o.aaa を書き換えても GetHashCode は変化しません。このため、
登録済みデータに合致する情報であるとみなされ、
2 回目の Add は実施されません。

そのため、★の件数表示で「1」と出力されます。

一方、GetHashCode をオーバーライドして o.aaa の変化に追従させた場合、
別データと看做されるため、両方とも登録され「2」と出力されます。
IPAddress の場合も 2 件ですね。


ただしこの場合、hs(0) と hs(1) が同じオブジェクトへの参照になってしまうため、
『If hs(0) = hs(1) Then』も True になってしまいます。
異なる要素に同じデータが入ってしまうのは、HashSet クラスの特性に向きません。

そのため、このような使い方が必要な場合は、後から内容が書き換えられないよう、
読み取り専用な不変クラス(あるいはそれに準じた実装)とすることを検討してみてください。




◆パターンB◆

 'HashSet.Add メソッドを 2 回呼び出しています。
 '
 '1 回目の Add と 2 回目の Add とで異なるインスタンスを
 '指定していますが、インスタンス双方は同じ値を有しています。
 '
 Dim q As New test() With {.aaa = "x", .bbb = "y", .ccc = "z"}
 Dim r As New test() With {.aaa = "x", .bbb = "y", .ccc = "z"}

 Dim hs3 As New HashSet(Of test)
 hs3.Add(q)
 hs3.Add(r)
 Console.WriteLine(hs3.Count)  '☆

 '(s1 Is s2) は「False」、(s1 = s2)は「True」
 Dim sb As New System.Text.StringBuilder("TEST")
 Dim s1 As String = sb.ToString()
 Dim s2 As String = sb.ToString()

 Dim hs4 As New HashSet(Of String)
 hs4.Add(s1)
 hs4.Add(s2)
 Console.WriteLine(hs4.Count)


このケースでは、☆ は「2」です。
もし、☆を「1」にしたい場合は、test クラスにさらに

 Public Overrides Function Equals(obj As Object) As Boolean
  Return TypeOf obj Is test AndAlso DirectCast(obj, test) = Me
 End Function

のように、Object.Equals をオーバライドすれば OK です。
Equals の既定の動作は参照性一致なので、「値の比較」にしたい場合は、
その動作を上書きする必要があるわけです。


HashSet について言えば、Object.Equls の代わりに IEquatable(Of ).Equals でも構いません。
Object.Equals と IEquatable(Of ).Equals の両方が実装されていた場合、
IEquatable(Of ) の方が優先されるようになっています。

Public Class test
 Implements IEquatable(Of test)
 Private Overloads Function Equals(other As test) As Boolean Implements IEquatable(Of test).Equals
  Return TypeOf other Is test AndAlso DirectCast(other, test) = Me
 End Function
 '
 '
 '
End Class



◆ Equals メソッド が True を返す場合、その 2 つのオブジェクトの
 GetHashCode メソッドは、必ず同じ値を返すように実装されていなければなりません。
 すなわち Equals を実装する際には、必然的に GetHashCode の実装も必要ということです。


◆ 2 つのオブジェクトの GetHashCode メソッドが同じ値を返したからといって
 必ずしも Equals メソッドが True になるとは限りません。
 極端な話、GetHashCode を「Return 固定値」で実装しても動作はします。
 (探索回数が増えるので、パフォーマンスは犠牲になりますが)


Equals をオーバーライドしたときに、等価演算子まで用意するかどうかは任意です。
しかし逆に等価演算子を実装した場合には、一緒に Equals メソッド
(それと GetHashCode) もオーバーライドした方が良いとおもいます。
先の例では、Equals を書き漏れていましたが…。
引用返信 編集キー/
■82680 / inTopicNo.9)  Re[6]: クラスで定義した変数を比較する方法
□投稿者/ スキン (5回)-(2017/01/26(Thu) 14:16:11)
解決しました

丁寧にどうもありがとうございました。
 
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -