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

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

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

StructureでNothingかどうか判定する方法

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

■92092 / inTopicNo.1)  StructureでNothingかどうか判定する方法
  
□投稿者/ る (1回)-(2019/08/24(Sat) 17:50:55)

分類:[.NET 全般] 

PointやPointFだと

If aaa IsNot Nothing Then

のようにしてNothingかどうか判定することができます。
しかし、自分で定義したStructureの場合には、
上記のようにしても「参照型を持つオペランドが必要ですが、このオペランドの値型は""です
というエラーが出てしまいます

一体どうすれば、カスタムStructureでもNothingを使うことができますか?



引用返信 編集キー/
■92093 / inTopicNo.2)  Re[1]: StructureでNothingかどうか判定する方法
□投稿者/ Hongliang (877回)-(2019/08/24(Sat) 19:29:48)
2019/08/24(Sat) 20:26:54 編集(投稿者)
私のVisual Studio 2019だとPointでも同様にコンパイルエラーになりますが?
そして仮にコンパイルできたとしても、構造体はPointでも自作構造体でも、Is Nothingは常に偽を返す
(IsNot Nothingであれば常に真を返す)ことになるのでその条件式は無意味です。
あるいはひょっとして、「If 構造体変数 = Nothing Then」という式でしょうか?

VBではNothingを2種類の意味に使っています。
a) .NETにおけるnull
b) 任意の型の既定値。クラス型の場合はnull、構造体型の場合は「全てのフィールドが0およびnullである構造体」

「Is Nothing」で出てくるNothingは上記aの意味で使われています。
nullはクラス型の変数だけに存在する状態であり、構造体型の変数には存在しません。
そのため「構造体型変数 Is Nothing」は演算の意味がないのでコンパイルエラーになります。

「Dim p As Point = Nothing」などで出てくるNothingは上記bの意味で使われますね。
「If 構造体変数 = Nothing Then」という式におけるNothingもこちらの意味になります。
この場合、構造体が演算子 =をオーバーロードしている必要があります。
https://www.atmarkit.co.jp/fdotnet/bookpreview/kisokaravb_1101/kisokaravb_1101_01.html

ただ、直感的ではないので「= Nothing」という比較式を記述するのはお勧めしません。
構造体にZeroとかEmptyとかそういうShared ReadOnlyなフィールドを用意しておいて、
それと比較するようにした方がわかりやすいです。

Structure Hoge
    Implements IEquatable(Of Hoge)
    Public Shared ReadOnly Empty As Hoge ' 明示的に代入する必要はない
    ' 以下のメソッドの実装は略
    Public Shared Operator =(ByVal left As Hoge, ByVal right As Hoge)
    Public Shared Operator <>(ByVal left As Hoge, ByVal right As Hoge)
    Public Overrides Function GetHashCode() As Integer
    Public Overrides Function Equals(ByVal obj As Object) As Boolean
    Public Overloads Function Equals(ByVal other As Hoge) As Boolean Implements IEquatable(Of Hoge).Equals
End Structure

If hogeObj = Hoge.Empty Then

引用返信 編集キー/
■92095 / inTopicNo.3)  Re[2]: StructureでNothingかどうか判定する方法
□投稿者/ る (2回)-(2019/08/24(Sat) 21:05:14)
ありがとうございます。

> 私のVisual Studio 2019だとPointでも同様にコンパイルエラーになりますが?

そうですね。
再確認したところ、以下のような場合には
isnot nothingが使えることが分かりました。

Dim aaa(1)() As Point

If aaa(1) IsNot Nothing Then
End If



> 「Dim p As Point = Nothing」などで出てくるNothingは上記bの意味で使われますね。


aの意味合いではなくbの意味合いで十分なので、
カスタムのStructureでも使えれば良いのですが
方法はございますでしょうか?


> Structure Hoge
> Implements IEquatable(Of Hoge)
> Public Shared ReadOnly Empty As Hoge ' 明示的に代入する必要はない
> ' 以下のメソッドの実装は略
> Public Shared Operator =(ByVal left As Hoge, ByVal right As Hoge)
> Public Shared Operator <>(ByVal left As Hoge, ByVal right As Hoge)
> Public Overrides Function GetHashCode() As Integer
> Public Overrides Function Equals(ByVal obj As Object) As Boolean
> Public Overloads Function Equals(ByVal other As Hoge) As Boolean Implements IEquatable(Of Hoge).Equals
> End Structure


これに関してですが、

エラー BC33005 'End Operator' が必要です。
エラー BC30289 ステートメントをメソッド本体の内部に表示することはできません。メソッド本体の終了と見なします。

とたくさんエラーが出ますが
どのように使えば良いですか?


引用返信 編集キー/
■92096 / inTopicNo.4)  Re[3]: StructureでNothingかどうか判定する方法
□投稿者/ 魔界の仮面弁士 (2319回)-(2019/08/24(Sat) 21:19:28)
No92095 (る さん) に返信
> 再確認したところ、以下のような場合には
> isnot nothingが使えることが分かりました。
> Dim aaa(1)() As Point
> If aaa(1) IsNot Nothing Then
> End If

配列は「参照型」だからです。なので (a) の構文として使えます。

Point 構造体は「値型」ですが、
Point の一次元配列は「参照型」です。

aaa(x)(y) は 値型な As Point を示しており
aaa(x) は 参照型の As Point() を示しており、
aaa は 参照型の As Point()() を示します。




>>「Dim p As Point = Nothing」などで出てくるNothingは上記bの意味で使われますね。

a の意味で使うなら、
 Dim z As Point? = Nothing
 If z Is Nothing Then
のようには書けますね。「Null 許容値型」と呼ばれます。
https://docs.microsoft.com/ja-jp/dotnet/visual-basic/programming-guide/language-features/data-types/nullable-value-types


> aの意味合いではなくbの意味合いで十分なので、
ならば既に書かれているように、Is Nothing ではなく = Nothing でしょう。
 Dim i As Integer = Nothing
 If i = Nothing Then
この場合の Nothing は b の「既定値」の意味であり、
 Dim i As Integer = 0
 If i = 0 Then
と同じ意味となります。
引用返信 編集キー/
■92097 / inTopicNo.5)  Re[4]: StructureでNothingかどうか判定する方法
□投稿者/ る (3回)-(2019/08/24(Sat) 21:47:22)

> ならば既に書かれているように、Is Nothing ではなく = Nothing でしょう。





Structure ddd
Property x As Integer
Property y As Integer
End Structure





Dim ggg As ddd

If ggg = Nothing Then


End If


としてもエラーとなりますが
どのようにしたら良いですか?


引用返信 編集キー/
■92100 / inTopicNo.6)  (削除)
□投稿者/ -(2019/08/25(Sun) 22:44:13)
この記事は(投稿者)削除されました
引用返信 編集キー/
■92101 / inTopicNo.7)  Re[6]: StructureでNothingかどうか判定する方法
□投稿者/ る (4回)-(2019/08/25(Sun) 23:03:16)
ありがとうございます。

しかし、これかなり面倒ですね。

プロパティーが2つくらいなら良いですが、
10個近くあるとかなり面倒臭いですね。

これをStructureの数だけ繰り返すとなると
更に面倒です。

自動で生成してくれれば楽なのですが
そのような方法はないですよね?

PointやPointFは、同じStructureでありながら、
内部でこういったコードが入っているのでしょうか?




引用返信 編集キー/
■92102 / inTopicNo.8)  Re[5]: StructureでNothingかどうか判定する方法
□投稿者/ 魔界の仮面弁士 (2321回)-(2019/08/25(Sun) 23:35:24)
操作ミスって削除してしまったので再送。

No92095 (る さん) に返信
> どのように使えば良いですか?
Hongliang さんはあくまでも、必要な定義を示してくれているだけです。
それぞれの Function や Operator の中身を実装するのは、るさん御自身の役目ということで。


No92097 (る さん) に返信
> としてもエラーとなりますが
> どのようにしたら良いですか?

構造体 ddd に何も手を加えないのなら、
 Dim ggg As ddd
 If ggg.Equals(CType(Nothing, ddd)) Then
のようにすることで、初期値判定することができます。


しかし、Nothing を先の a 判定のために
 If ggg Is Nothing Then
のように使いたいのであれば、
「Dim ggg As ddd」を「Dim ggg As ddd?」に変更するか、もしくは、
型 ddd を 構造体 からクラスに置き換える必要があります。


また、Nothing を先の b の意味で使い、
 If ggg = Nothing Then
としたい場合には、Hongliang さんが書かれていたように、
構造体に対して等価演算子をオーバーロードする必要があります。


つまり、下記の実装が必要になるという事です。

(1) 『Public Shared Operator =(l As ddd, r As ddd) As Boolean』を実装する。
(2) 『Public Shared Operator <>(l As ddd, r As ddd) As Boolean』を実装する。
(3) Object.Equals をオーバーライドする。『Public Overrides Function Equals(obj As Object) As Boolean』
(4) Object.GetHashCode をオーバーライドする。『Public Overrides Function GetHashCode() As Integer』

ここまでが最低限必要となるものです。(コンパイルを通すだけなら、1と2だけでも動きますが…)
可能ならばこれに加えて、IEquatable(Of ddd) インターフェイスも実装することが望ましいです。


Visual Studio 2017 以降をお使いであれば、構造体宣言の
「Structure ddd」と書かれた行のどこかにカーソルがある状態で
 (a) 左端の「豆電球」アイコンをクリックする
 (b) Alt + Enter を押す
 (c) Ctrl + . を押す
のいずれかの操作を行ってみてください。

ポップアップメニューが表示されるので、そこから『Equals および GetHashCode を生成する...』を
クリックした上で、その次のダイアログで『演算子を生成する』のチェックを入れて OK してみてください。
それだけで上記 4 つがすべて自動で実装されますので、手間もほとんどかかりません。
https://docs.microsoft.com/ja-jp/visualstudio/ide/reference/generate-equals-gethashcode-methods?view=vs-2017

※自動生成されるコードの内容は、ターゲットとなる .NET Framework バージョンによって異なっており、
 より新しいバージョン向けの方が、よりスマートなコードとして生成されます。


VS2017 未満のバージョンをお使いの場合は、これらの実装を自ら実装する必要があります。
具体的にはこんな感じです。


Public Structure ddd
  ' 等価性判定のためのインターフェース。無くても動くけど、「不変性のある構造体」の場合は付与した方が望ましい。
  'Implements IEquatable(Of ddd)

  Public Property x As Integer
  Public Property y As Integer

  '【今回の肝】「= 演算子」のオーバーロード
  Public Shared Operator =(l As ddd, r As ddd) As Boolean
    Return l.x = r.x AndAlso l.y = r.y ' 各メンバーが等しければ、同じ値と見なす
  End Operator

  '【今回の肝】「<> 演算子」のオーバーロード
  Public Shared Operator <>(l As ddd, r As ddd) As Boolean
    Return Not (l = r) ' このように、= 演算子を Not 判定して書くことが多い
  End Operator

  '【今回の肝】 Object.Equals(Object) をオーバーライド
  Public Overrides Function Equals(obj As Object) As Boolean
    ' 型が ddd であった場合は「= 演算子」で一致判定。違う型なら常に False。
    Return TypeOf obj Is ddd AndAlso DirectCast(obj, ddd) = Me
  End Function

  '【今回の肝】 Object.GetHashCode() をオーバーライド
  Public Overrides Function GetHashCode() As Integer
    Return x Xor y
  End Function

  '' Equals(ddd) なオーバーロード メソッド
  'Public Overloads Function Equals(other As ddd) As Boolean Implements IEquatable(Of ddd).Equals
  '  Return Me = other ' 実際の比較は、オーバーロードした = 演算子で行う
  'End Function
End Structure
引用返信 編集キー/
■92103 / inTopicNo.9)  Re[7]: StructureでNothingかどうか判定する方法
□投稿者/ 魔界の仮面弁士 (2322回)-(2019/08/25(Sun) 23:40:49)
2019/08/26(Mon) 00:03:39 編集(投稿者)

No92101 (る さん) に返信
> 自動で生成してくれれば楽なのですが
> そのような方法はないですよね?

先に述べた通り、VS2017 以降なら自動生成の支援が受けられます。


> 10個近くあるとかなり面倒臭いですね。

要件にもよりますが、そもそも比較演算子まで実装するような構造体に、
それほど多数のメンバーを含めるべきではありません。

構造体のサイズは 16バイト未満で、メンバーに参照型を含まず、
かつ、不変であることが望ましいです。
https://docs.microsoft.com/ja-jp/dotnet/standard/design-guidelines/choosing-between-class-and-struct


メンバーが 10 個あるのなら、クラスに置き換えることも検討してみてください。
クラスなら、先の Is Nothing 判定もできるわけですし。


> PointやPointFは、同じStructureでありながら、
> 内部でこういったコードが入っているのでしょうか?

入っています。たとえばこのあたりですね。
https://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Point.cs,168

周りを見ると、「+ 演算子」や「- 演算子」も実装されていることが見て取れるかと思います。
引用返信 編集キー/
■92104 / inTopicNo.10)  Re[6]: StructureでNothingかどうか判定する方法
□投稿者/ 魔界の仮面弁士 (2323回)-(2019/08/26(Mon) 00:18:37)
No92102 (魔界の仮面弁士) に追記
> 具体的にはこんな感じです。

長くなりますが、ざっくりと解説。


自作のクラスや構造体を『= 演算子』で判定するという事は、一致性すなわち、
比較式の両辺が「同じ値とみなせるかどうか」を定めねばならないということを意味します。

この一致性というのは、必ずしもバイナリレベルで一致している必要はありませんので、
何をもって「同じ」と見なすかは、実際にはそのデータ型の実装依存と言えます。
たとえば、Deciaml 構造体のように。

' x と y は同じ値ではあるけれども、有効桁数が異なっている
Dim x As Decimal = CDec("1.2000")
Dim y As Decimal = CDec("1.20")

' = 演算子は True となる
Dim b1 As Boolean = (x = y)

' Equals(Object) メソッドも True となる
Dim b2 As Boolean = x.Equals(y)
Dim b3 As Boolean = Object.Equals(x, y)

'しかし有効桁数が異なっているため、内部バイナリは異なっており、下記は False となる
Dim b4 As Boolean = (x.ToString() = y.ToString())
Dim b5 As Boolean = Decimal.GetBits(x).SequenceEqual(Decimal.GetBits(y))


……という事を踏まえて、先のコードの解説。


=== Equals(Object) メソッドについて ===
> '【今回の肝】 Object.Equals(Object) をオーバーライド
> Public Overrides Function Equals(obj As Object) As Boolean

Equals(Object) メソッドは Object 型によって実装された Overridable なインスタンスメソッドであり、
すべての型で利用可能かつオーバーライド可能なメソッドです。


文字通り、その値が「同じかどうか」を返す物であり、基本的には
 ・型が同じであること
 ・型が指す内容が、同等であるとみなせること
の両方を満たした場合に True となるように実装します。
※クラスの場合はさらに、「型が同じでインスタンスが異なる場合を許可するか否か」の判定を入れることも。

このメソッドを使って一致判定をする場合には、たとえば
  Dim a As Integer = 1
  Dim b As Long = 1
  If a.Equals(b) Then '型が違うので False
のように使えます。

No92102 で述べた「If ggg.Equals(CType(Nothing, ddd)) Then」な処理が実行できるもの、
オーバーライドせずとも、Object 自身が Equals メソッドを持っているためです。

構造体の Equals をオーバーライドしなかった場合、ValueType.Equals メソッドによる
既定の実装が使われますが、これは、リフレクションによる実行時解析を伴うため、比較的低速です。

そのため、List や Dictionary に保持して使うような型の場合には、
Equals メソッドも実装しておくことが望ましいです。パフォーマンス的な意味で。



=== GetHashCode() メソッドについて ===
> '【今回の肝】 Object.GetHashCode() をオーバーライド
> Public Overrides Function GetHashCode() As Integer

これも Object 型によって実装された Overridable なメソッドであり、
常に Integer 値を返すようになっています。このメソッドは、
自身が持つ値を示すハッシュ値を `素早く` 返すことが求められます。

どういう値を返せばよいかと言えば、たとえば
  If a.Equals(b) Then
な処理において『Equals が True を返す場合』には、a.GetHashCode() と b.GetHashCode() が、
必ず両者が『同じ Integer 値を返す』ことを保証しなければならないルールになっています。

そのため Equals を実装した場合には、GetHashCode も一緒に実装することが求められます。



=== 演算子のオーバーロードについて ===
> '【今回の肝】「= 演算子」のオーバーロード
> Public Shared Operator =(l As ddd, r As ddd) As Boolean

> '【今回の肝】「<> 演算子」のオーバーロード
> Public Shared Operator <>(l As ddd, r As ddd) As Boolean


今回使おうとしていた「If ggg = Nothing Then」のためには、当然、= 演算子の実装が必須となります。
また、= 演算子を実装する場合には、対となる <> 演算子も実装しなけれならないルールです。

これについては、2 つの引数の一致性を見るだけなので、実装はさほど難しくないでしょう。

必要であれば、大小比較の演算子を追加したり、ddd 以外の型との比較を行えるようにもできます。
演算子のオーバーロードについては、最初に Hongliang さんが投稿くださった URL で解説されていますね。




=== IEquatable(Of ).Equlas メソッドの実装について ===
> ' 等価性判定のためのインターフェース。無くても動くけど、「不変性のある構造体」の場合は付与した方が望ましい。
> 'Implements IEquatable(Of ddd)

> ' Equals(ddd) なオーバーロード メソッド
> ' Public Overloads Function Equals(other As ddd) As Boolean Implements IEquatable(Of ddd).Equals

この実装は、不変(Immutable)な型に対して付与されます。
具体例としては、たとえば「System.Version 構造体」や「System.String クラス」などです。

※可変(Mutable)… インスタンスの生成後に、その値を変更できる型。
※不変(Immutable)… 一度インスタンスを生成したら、値を変更できない型。

機能的には、Function Equals(obj As Object) As Boolean と一緒ですが、
引数が Object 型ではなく、固有の型になっている点が最大の特徴です。

引数が Object のままですと、構造体では「ボックス化(boxing)」と「ボックス化解除(unboxing)」が
発生してしまいますし、コンパイル時にも明らかに無関係な型を渡せてしまいます。

Function Equals(other As ddd) As Boolean なオーバーロードを設けておけば、
無関係な型を渡すこともなくなりますし、無用なボックス化も省くことができるため、
比較処理のパフォーマンス向上が期待できます。
引用返信 編集キー/
■92105 / inTopicNo.11)  Re[7]: StructureでNothingかどうか判定する方法
□投稿者/ 魔界の仮面弁士 (2324回)-(2019/08/26(Mon) 02:17:34)
2019/08/26(Mon) 02:18:14 編集(投稿者)

No92104 (魔界の仮面弁士) に追記
> 長くなりますが、ざっくりと解説。

追記の追記。

> === IEquatable(Of ).Equlas メソッドの実装について ===
> この実装は、不変(Immutable)な型に対して付与されます。
> 具体例としては、たとえば「System.Version 構造体」や「System.String クラス」などです。
> ※可変(Mutable)… インスタンスの生成後に、その値を変更できる型。
> ※不変(Immutable)… 一度インスタンスを生成したら、値を変更できない型。


今回の ddd 構造体に IEquatable を付与するのは望ましくないようです。
自分の認識としては、下記のようになっています。

(1) 値型は基本的に Immutable[不変]にすべきであり、その場合、IEquatable を実装すると共に、Equals および等価演算子もフルセットで実装する。

(2) Mutable[可変]なクラスは、IEquatable を実装すべきではない。Equals は、その参照型が「値」として意味を持つ場合に限り実装する。

(3) Immutable[不変]なクラスは IEquatable を実装してよいが、その場合は Equals() も実装する。ただし等価演算子については、
  その参照型が「値」として意味を持つ場合に限って実装する。


根拠としてはこのあたり。

https://docs.microsoft.com/ja-jp/dotnet/api/system.object.equals?view=netframework-4.8
https://docs.microsoft.com/en-us/dotnet/api/system.object.equals?view=netframework-4.8

日本語版は機械翻訳の質が酷いので、英語原文の方を意訳してみます。
VS2008(.NET 3.5) 当時のものと比べてみると、記述が微妙に変わっているようで…。


【値型(構造体)】のガイドライン

・値型の中に、参照型のフィールドが 1 つ以上含まれている場合は、既定の ValueType.Equals 実装よりも高いパフォーマンスを引き出すため、
 Equals をオーバーライドすることを検討します(SHOULD)。
 ※VS2008 当時も Equals のオーバーライドが推奨されてはいたが、参照型フィールドへの言及はされていなかった。

・開発言語が演算子および Equals のオーバーロードをサポートしている場合は、等価演算子もオーバーロードしてください(MUST)。
 ※ここは VS2008 当時と変わらず。

・IEquatable(Of ) インターフェイスを実装して、ボックス化を避けて強い型付けの Equals メソッドが呼ばれるようにします(SHOULD)。
 ※この一文は、VS2008 当時の資料には記載なし。さらに言えば、そもそも構造体は不変(immutable)にするべきとのこと。



【参照型(クラス)】のガイドライン

・その参照型が、実在する値に基づいた役割(セマンティクス)を持つ場合、 Equals をオーバーライドすることを検討します。
 ※ここは VS2008 当時と変わらず。

・もしも Equals をオーバーライドするとしても、ほとんどの参照型においては、等値演算子をオーバーロードしないでください(MUST NOT)。
 ただし複素数型などのように、値のセマンティクスを持つことを目的とした参照型を実装する場合に限っては、
 等値演算子をオーバーライドする必要があります(MUST)。
 ※ここも VS2008 当時と変わらず。

・可変な(mutable)参照型では、 Equals をオーバーライドしないでください(SHOULD NOT)。
 前のセクションで説明されているように、Equals メソッドのオーバーライドでは、GetHashCode もオーバーライドされる必要があるためです。
 これはすなわち、可変な参照型のインスタンスでは、オブジェクトの有効期間の間にハッシュ値が変更されることがあり、
 それにより、ハッシュテーブルがオブジェクトを見失う可能性があるという事を意味します。
 ※この一文は、VS2008 当時の資料には記載なし。



なお、上記の「値型は不変とすべき」という点は、下記の構造体デザインガイドに記載があります。
こちらも VS2008 当時の資料比較しながら意訳。

https://docs.microsoft.com/ja-jp/dotnet/standard/design-guidelines/struct
https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/struct


・引数無しの既定のコンストラクタを提供してはいけません。
 ※VS2008 当時と変わらず。というか、そもそも VB/C# では構造体で既定のコンストラクタを使用できない。

・可変な(mutable)値型を定義してはいけません。
 可変な値型には様々な問題があります。たとえば、プロパティの get アクセッサが値型を返す場合、
 呼び出し元が受け取るのは値型のコピーであるため、開発者がコピーの値を可変したとしても、それは元の値ではありません。
 また、動的言語などの一部の言語においては、コピーによるローカル変数の逆参照時に問題を引き起こす可能性があります。
 ※VS2008 当時の資料には記載なし。

・すべてのインスタンス データに、ゼロ、False、(必要であれば)Nothing がセットされた状態が有効であることを確認します。
 これにより、構造体の配列が作成されるときに誤って無効なインスタンスが作成されるのを防ぎます。
 ※VS2008 当時も同じ記述があったが、当時は具体的なサンプルコードも併記されていた。

・値型には System.IEquatable(Of ) を実装してください。
 値型における System.Object の Equals(Object) メソッドはボックス化を引き起こし、その既定の実装は
 リフレクションを使用するため、あまり効率的ではありません。System.IEquatable(Of T) の Equals(T) メソッドを
 使用することで、パフォーマンスが大幅に向上し、ボックス化が発生しないように実装できます。
 ※VS2008 当時も同じことが書かれていたが、もう少し簡素な表現だった。

・ValueType 型を明示的に拡張(extend)しないでください。実際の所、殆どの言語でこれは回避されます。
 一般に、構造体は非常に便利ですが、頻繁にボックス化されることのない、小さく単一な不変の値にのみ使用する必要があります。
 ※VB/C# ではそもそも ValueType を継承できない。VS2008 当時も同じ記述があったが、“small, single, immutable values”の一文は無かった。
引用返信 編集キー/

このトピックをツリーで一括表示


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

このトピックに書きこむ