トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

C#/問題と回避策

一部の情報は非常に古いもの(20年以上前〜)ですので、利用する際はご注意ください(Java 1.4 とか .NET 1.0 とか、Windows 2000 とか)
お問い合せは wiki@shise.net まで。Gmail に転送されるので、スパムは全部カットされます。


 

目次


2ギガを超えるファイルを StreamWriter で書き込むと例外が投げられる。


以下のようなコードを実行すると、ファイルサイズが2ギガを超えた時点で例外が投げられる。

Encoding enc = Encoding.GetEncoding(932);
string str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\r\n";

string filepath = Path.GetTempFileName();
long count = 0;
try
{
    // ファイルを新規作成
    using (StreamWriter writer = new StreamWriter(filepath, false, enc))
    {
        while (true)
        {
            writer.Write(str);

            if (count++ > 100000)
            {
                count = 0;
                Console.Write("{0:#,0} KB\r", writer.BaseStream.Position / 1024);
            }
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine();
    Console.WriteLine(ex);
}
finally
{
    try
    {
        File.Delete(filepath);
    }
    catch { }
}

例外は、こんな感じ。

System.ArgumentException: GetBytes() または GetByteCount() を呼び出す前に、Convert() 操作を完了するか、または Encoder.Reset() を呼び出さなければなりません。エンコーダ '日本語 (シフト JIS)' フォールバック 'System.Text.InternalEncoderBestFitFallback'。
 場所 System.Text.DBCSCodePageEncoding.GetBytes(Char* chars, Int32 charCount, Byte* bytes, Int32 byteCount, EncoderNLS encoder)
 場所 System.Text.EncoderNLS.GetBytes(Char* chars, Int32 charCount, Byte* bytes, Int32 byteCount, Boolean flush)
 場所 System.Text.EncoderNLS.GetBytes(Char[] chars, Int32 charIndex, Int32 charCount, Byte[] bytes, Int32 byteIndex, Boolean flush)
 場所 System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder)
 場所 System.IO.StreamWriter.Dispose(Boolean disposing)
 場所 System.IO.TextWriter.Dispose()

この2ギガ問題を解決するには・・・
ファイルサイズが2ギガを越える前に、一度ストリームを閉じて、
その後、ファイルに追記する形でストリームを開きなおすと良い。

具体的なコードは以下の通り。

Encoding enc = Encoding.GetEncoding(932);
string str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\r\n";

string filepath = Path.GetTempFileName();
long count = 0;

long cycle = 1 * 1024 * 1024 * 1024; // 1G
long streamsize = cycle;

// ファイルを新規作成
StreamWriter writer = new StreamWriter(filepath, false, enc);
try
{
    while (true)
    {
        writer.Write(str);

        if (count++ > 100000)
        {
            count = 0;
            Console.Write("{0:#,0} KB\r", writer.BaseStream.Position / 1024);
        }

        if (writer.BaseStream.Position > streamsize)
        {
            streamsize += cycle;
            writer.Flush();
            writer.Close();
            // ファイルに追記する
            writer = new StreamWriter(filepath, true, enc);
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine();
    Console.WriteLine(ex);
}
finally
{
    try
    {
        writer.Close();
    }
    catch { }
    try
    {
        File.Delete(filepath);
    }
    catch { }
}

こんな感じ。
ここでは、1ギガごとに、ストリームを作り直すようにした。

ちなみに、using を使うと、変数が読み取り専用になるらしく?、StreamWriter を作り直せないので、はずした。
そのため、 finally で閉じ忘れないようにした。

object 型にキャストした値型の扱いについて。

次のようなコードを実行すると、どうなるだろうか。

double d = 1.23456;
int i = (int)d;
Console.WriteLine(i);

これは問題なく動作する。

では、次のコードはどうだろうか。

double d = 1.23456;
object o = d;
int i = (int)o;
Console.WriteLine(i);

このコードは動作しない。
object 型から int へキャストした時点で

ハンドルされていない例外: System.InvalidCastException: 指定されたキャストは有効ではありません。

といった Exception がスローされる。

じゃー、一旦 object 型になってしまった、何らかの値の型を別な値の型に変換したいときはどうするかというと、Convert クラスを使う。
上の例では、

int i = Convert.ToInt32(o);

とすることで変換できる。

なお、 object 型にする値は double 型でなくても、全ての値型で起る。

TcpClient をハブだけの環境で使うと遅い気がするので。

.Net Framework には、TcpClient という Socket のラッパっぽい、便利クラスがある。

だが、このクラスを使って、ハブだけのクローズドな環境で利用すると、接続開始してから、何かのタイムアウト待ち?で、通信できるまで15秒かかってしまう。
デフォルトゲートウェイがある環境(ルーターがある環境?)ではすぐ通信できるが、無い環境では15秒後に通信が始まる。
Socket クラスを使用すると、すぐに通信できるのを見ると、TcpClient は恐らく、何らかの処理(名前解決とか?)を行おうとして、そのタイムアウトが15秒に設定してあるためと思われる。

なので、TcpClient と同じようなメソッドを実装したクラスで、既存の TcpClient の実装部分を置き換えることでコレが解決する。
具体的には、以下のようなクラスを作成した。

そーす

IPAddress.Broadcast をハブだけの環境で使うと届きにくい気がするので。


上の問題と同じく、UDPブロードキャストのアドレスに、IPAddress.Broadcast を使った場合の話。
ルーターがあれば問題無いが、ハブだけの環境だと、届いたり、届かなかったりする。
IPAddress.Broadcast の中身は、255.255.255.255 となっている。

で解決したのは

// 自分のIPアドレスを取得
IPAddress addr = Dns.Resolve(Dns.GetHostName()).AddressList[0];

// バイト列にする。
byte[] b = addr.GetAddressBytes();

// 最後だけ255にする。
IPAddress broadcast = IPAddress.Parse(b[0]+"."+b[1]+"."+b[2]+".255");

こんな感じで、自分のアドレスの最後だけ 255 にすることでルーターの無い環境でもブロードキャストがすぐ届く気がする。

自分のアドレスが 192.168.10.2 だったら、 broadcast は、192.168.10.255 になる。


---追記
友人の知人の方からのご指摘で、サブネットマスク考慮しねーとダメなんじゃないの?との話でしたので、サブネットマスクを考慮したアドレスを生成できるクラス作りました。

サブネットマスクを考慮した 255 なアドレスが必要な人は、こちら を参照してください。

Hashtable のキーに バイト配列(byte[])を使用した際の問題

 問題

問題の検証用のコード

Hashtable table = new Hashtable();

byte[] key1 = new byte[]{0,1,2,3,4};
table[key1] = "VALUE";

byte[] key2 = new byte[]{0,1,2,3,4};
Console.WriteLine(table[key2]);    

内容の同じ key1 と key2 のバイト列を用意する。
key1 をキーに対して "VALUE" という値をハッシュテーブルに代入する。
しかし、key1 と全く同じ内容の key2 で値を取り出そうとしても取り出すことが出来ない。
(key1 で取り出すことは出来る。)

 実験

同じ内容の二つの配列で、比較してみた。

if(key1 == key2)
{
	Console.WriteLine("key1 = key2");
}
else
{
	Console.WriteLine("key1 != key2");
}


if(key1.Equals(key2))
{
	Console.WriteLine("key1 = key2");
}
else
{
	Console.WriteLine("key1 != key2");
}


if(Array.Equals(key1, key2))
{
	Console.WriteLine("key1 = key2");
}
else
{
	Console.WriteLine("key1 != key2");
}


if(key1.GetHashCode() == key2.GetHashCode())
{
	Console.WriteLine("key1 = key2");
}
else
{
	Console.WriteLine("key1 != key2");
}

これらの結果はすべて「key1 != key2」となる。

 解決案

どうやら、key1 と key2 は、全く異なるオブジェクトとして存在しているようだ。(当たり前だが。)
さて、どうしたものか・・・。

その1:自力で検索する

思いつく限りでは要素を一つ一つ比較するのが簡単かと思う。

public static bool byteEqual(byte[] a, byte[] b)
{
	if(a.Length != b.Length)
	{
		return false;
	}

	int len = a.Length;
	for(int i=0; i<len; i++)
	{
		if(a[i] != b[i])
		{
			return false;
		}
	}

	return true;
}

バイト配列 a と b の要素を一つ一つ比べて、一つでも違っていれば false 。全く同じであれば ture を返す。
配列が同じかどうか分かれば、あとは Hashtable からキーを取り出し、同じものを探し出した後、そのキーと対になる値を返せばなんとかなる。

public static object searchTable(byte[] key, Hashtable table)
{
	IDictionaryEnumerator e = table.GetEnumerator();
	while(e.MoveNext())
	{
		byte[] b = (byte[])e.Key;
		if(byteEqual(key, b))
		{
			return e.Value;
		}
	}
	return null;
}

一応、これでキーに byte[] 配列を入れたときの検索は出来るようになったが、ここまでして Hashtable を使用する意味があるかどうかは分からない。

もっと賢い方法がありそうだけど、思いつかないので、教えてエロい人。

---2005/10/13 追記
ペンティアム4、2.4G の WindowsXP SP2 のマシンで測定してみたが、16バイトx1万のデータを用意して、1000回検索を行ったところ、ちょうど1秒かかった。
これでも十分実用に耐えうると考える。
ただ、この1秒がもったいないという場合は、他のチューニングが必要だろう。

その2:コンパレータを実装する(by Kejiko氏)

Javaで、コンパレータ(順序付け処理?)の実装による解決。
同じように C# でも実装が可能。(だと思う)

そーす(*注意 Java 5.0 の ジェネリックを使用したソース)

その3:バイト列用の連想配列


実験そーす

その4:文字列として扱う。

美しくないかもしれないが、バイト列を文字列に変換することで検索が可能になる。
以下のようなメソッドを使用することで文字列に変換する。

BitConverter.ToString(b)

バイト列を文字列に変換した後は、後は Hashtable なり、なんなり使用することが出来る・・・が、意外に処理が遅いかもしれない。

---
これ超早かったです。
使い方も簡単だしこれ使った方がいいかも。

ネットワークドライブ上での実行の問題

 問題

ネットワークドライブ上の.NETアプリケーションはデフォルトでアクセス権が低く設定されている。そのため、ネットワークドライブ上から実行した場合、アクセス権限の関係で例外が投げられる可能性がある。(PolicyException など)

 解決策

ローカルドライブ上にコピーする
ローカルにコピーすると、フルアクセス権があたえられ、上記の問題は解決する。
.NET Framework の セキュリティーポリシーを変更1(要、管理者権限)
.NET Framework ウィザードにて、「アセンブリの信頼」→「このコンピュータへ変更を行う」→ファイルへのパスを参照→「信頼なし」を「完全な信頼」に設定→「完了」。これで対象の実行ファイルにフルアクセス権が与えられる。
.NET Framework の セキュリティーポリシーを変更2(要、管理者権限)
.NET Framework 構成にて「ランタイムセキュリティポリシー」→「エンタープライズ」→「コードグループ」→「All_Code」を右クリックして「プロパティ」→「このレベル以下のポリシーレベルは評価しない」をチェック→「OK」。これで、このマシンで実行されるすべての.NET Framework アプリケーションにフルアクセス権があたえられる。

 備考

他にも証明書などによる信頼も可能らしいが未調査。
無理っぽいかな。

 参考