2009年3月29日日曜日

.NET Integer型の乗算でオーバーフローが発生する

Integer型の乗算でオーバーフローが発生する場合があります。
以下のコードでは変数i1とi2の乗算を行っています。
計算の結果がInteger型の範囲に収まらないのは分かっているので、
結果を収める変数をLong型にしましたがオーバーフローが発生します。

オーバーフローを起こさないためには計算に使用する最初の変数i1をLong型にキャストします。

Dim i1 As Integer = 12345678
Dim i2 As Integer = 123456

Dim lResult As Long 

'オーバーフローが起きる
lResult = i1 * i2

'オーバーフローが起きる
lResult = Clng(i1 * i2)

'オーバーフローは起きない
lResult = Clng(i1) * i2


VisualBasicはコンパイル時に、最初の計算に使用する値の型から結果をInteger型と仮定します。
変数i1と変数i2の乗算の結果をIntgerと仮定し、式の結果がIntgerの範囲に収まらないためオーバーフローします。
コンパイル時に結果をLong型と仮定するためには、式の最初の変数i1をLong型にキャストします。

常識らしいです・・・知らなかった・・・(;・∀・)

.NET Boolean 型が数値型に正確に変換されない

Boolean型の値を数値型に変換する場合、CintとConvert.ToInt32で結果が異なります。
CintではTrueは-1、Falseは0に変換されます。
Convert.ToInt32ではTrueは1、Falseは0に変換されます。

MSDN:データ型のトラブルシューティングから引用

ブール型 (Boolean) (Visual Basic) 値は数字としては格納されず、格納された値は数字と等しくなりません。以前のバージョンとの互換性維持のため、Visual Basic には、Boolean 型と数値型を相互に変換するための変換用のキーワード (CType 関数、CBool、CInt など) が用意されています。ただし、他の言語では、この変換が別の方法で実行されることもあります。たとえば、.NET Framework のメソッドがそうです。

コードを作成する際、True および False に相当する数値を利用しないようにしてください。ブール型 (Boolean) の変数に対しては、できるだけ本来の論理値だけを使用してください。ブール型 (Boolean) と数値を混在させる必要がある場合は、選択した変換メソッドを十分理解してから行ってください。

Visual Basic における変換
変換用の CType キーワードまたは CBool キーワードを使って数値型を Boolean 型に変換すると、0 は False になり、それ以外のすべての値は True になります。変換用のキーワードを使って Boolean 値を数値型に変換すると、False は 0 になり、True は -1 になります。

フレームワークにおける変換
System 名前空間にある Convert クラスの ToInt32 メソッドを使うと、True は +1 に変換されます。

Boolean 値を数値データ型に変換する必要がある場合は、使用する変換メソッドに注意してください。

2009年3月17日火曜日

.NET OpenFileDialogやSaveFileDialogでカレントディレクトリが変更される事がある

OpenFileDialogやSaveFileDialogでカレントディレクトリが変更される事があります。
かなり有名な問題なのですが、いつも忘れてしまうのでメモしておきます。

ちなみにこの問題はVISTAでは発生しません。XPでは発生します。

現象を確認するため以下のコードをXPで実行します。
1回目のCurrentDirectoryは実行パスのディレクトリが返ります。
2回目のCurrentDirectoryはOpenFileDialogで指定した保存先パスのディレクトリが返ります。
'現在のカレントディレクトリ
Console.WriteLine(System.IO.Directory.GetCurrentDirectory)

'ファイルを開くダイアログ
Dim path As String = String.Empty
Using dialog As New OpenFileDialog
If dialog.ShowDialog = Windows.Forms.DialogResult.OK Then
path = dialog.FileName
End If
End Using

'現在のカレントディレクトリ
Console.WriteLine(System.IO.Directory.GetCurrentDirectory)


たとえば、このような現象で問題になるのは以下のようなコードがあります。
Dim asbl As System.Reflection.Assembly
asbl = System.Reflection.Assembly.LoadFrom("ClassLibrary.dll")

相対パスでアセンブリをロードしているのですが、通常はカレントディレクトリが実行パスのディレクトリであるため
実行パスのディレクトリからClassLibrary.dllを探します。
しかしOpenFileDialogやSaveFileDialogを使用した後、カレントディレクトリが保存先のディレクトリに変更されるため
保存先のディレクトリからClassLibrary.dllを探し、ClassLibrary.dllが見つからないのでエラーになります。



この問題を回避するため、OpenFileDialogやSaveFileDialogではRestoreDirectoryプロパティをTrueに設定するようにします。
RestoreDirectoryプロパティはダイアログ ボックスを閉じる前に、現在のディレクトリを復元するかどうかを示す値を取得または設定します。
デフォルトはFalseです。
'現在のカレントディレクトリ
Console.WriteLine(System.IO.Directory.GetCurrentDirectory)

'ファイルを開くダイアログ
Dim path As String = String.Empty
Using dialog As New OpenFileDialog
dialog.RestoreDirectory = True
If dialog.ShowDialog = Windows.Forms.DialogResult.OK Then
path = dialog.FileName
End If
End Using

'現在のカレントディレクトリ
Console.WriteLine(System.IO.Directory.GetCurrentDirectory)



一応このプロパティを設定することで問題は起きないはずです。
しかし、そもそもの原因はカレントディレクトリが実行パスのディレクトリだという事を前提にしたプログラムが一番の問題なのだと思います。
実行パスのディレクトリからClassLibrary.dllを探したいのなら、Application.StartupPathを指定しましょう。
Dim asblNm As String = System.IO.Path.Combine(Application.StartupPath, "ClassLibrary.dll")
Dim asbl As System.Reflection.Assembly
asbl = System.Reflection.Assembly.LoadFrom(asblNm)