山口屋~活動日誌~

私生活で主な出来事をピックアップ

C# デストラクタ ファイナライザ IDisposable using close 継承 委譲 ラッパー

2023-11-21 23:54:04 | ソフトウェア開発
C#のクラスで、別のC#のクラスをラップする場合、デストラクタはどう実装すれば良いのだろうか。C#の場合、デストラクタが呼び出されるタイミングは不定である。

C++/CLIのクラスで、C++クラス(ネイティブコード)をラップする場合は、ネイティブコードのインスタンスを生成して代入するネイティブコードのポインタをメンバとして定義、ポインタに対する処理でメソッドを定義、ファイナライザを定義してデストラクタから呼出、というように記述する。
では、C#のクラスではどうか?以下に。
デストラクタ
・マネージリソースの解放:書かない
・アンマネージ(ネイティブ)リソースの解放:書く
ファイナライザ(引数なしDisposeメソッド)
・マネージリソースの解放:書く
・アンマネージ(ネイティブ)リソースの解放:書く

<参考>
Qiita:デストラクタとDisposeについて
Qiita:C# のファイナライザ、Dispose() メソッド、IDisposable インターフェースについて

IDisposable インターフェースの実装は、bool disposing を引数に持つDisposeメソッドをオーバーライドし、マネージリソースの解放は disposing が ture のときのみ実行するように記述し、デストラクタには Dispose (false) 、ファイナライザ(引数なしDisposeメソッド)には Dispose (true) で呼び出す記述をする。また、ファイナライザ(引数なしDisposeメソッド)には、明示的に引数なしDisposeメソッドを呼び出した場合等にガベージコレクタで二度目の呼び出しを行わないよう、GC.SuppressFinalize(this) を記述する。

<参考>
++C++; // 未確認飛行 C:Dispose にまつわる余談 - C# によるプログラミング入門
@IT:.NET TIPS - 確保したリソースを忘れずに解放するには?

ここまでの話は、using を使ってインスタンスを作成するクラスを継承する際にどうしたらよいかという疑問点から調べたものである。しかし、何でも継承するのではなく、委譲する(ラッパーを作成する)という選択肢も考えるべきのようだ。FileStream クラスのインスタンスを StreamReader クラス、StreamWriter クラスのコンストラクタに渡すと、読み、書きのどちらかに機能が決まるので、これも一種の委譲なのかもしれない。

<参考>
ikenox.info:継承と委譲の使い分けと、インターフェースの重要性について
Qiita:君の継承の使い方は間違っている

CSVファイル C# TextFieldParser 文字列 構文解析 MemoryStream

2023-11-21 12:45:49 | ソフトウェア開発
Comma-Separated Values (CSV) の標準仕様としては下記がある。
RFC4180 : Common Format and MIME Type for Comma-Separated Values (CSV) Files
解説は様々なサイトがあるが下記の例は簡潔で、スペースや空白についての説明が他では無いことが多いので参考になった。
知識データベース:CSVフォーマット
・RFC準拠:前後にスペースがあっても無視しない
・RFC未定義:ダブルクォーテーションで囲まれているフィールドの前後に空白がある

CSVファイルの入力を行うのであれば、VB.NET用のTextFieldParserクラスを利用するのが楽だ。
・.NET Tips (VB.NET,C#...):CSV形式のファイルをDataTableや配列等として取得する

TextFieldParserクラス(Microsoft.VisualBasic.FileIO名前空間)は.NET Framework 2.0以降のVisualBasic用の機能として用意され、C#でも参照設定でMicrosoft.VisualBasic.dllを追加することが可能で、CSV(Comma-Separated Values)ファイルの解析でよく利用されるが、文字列の解析にも利用することができる。

ダブルクォーテーション中であっても空行は読み飛ばす点に注意。
Microsoft Docs:TextFieldParser.ReadFields メソッド (Microsoft.VisualBasic.FileIO)
TrimWhiteSpaceプロパティは、trueにするとダブルクォーテーション中でも前後の空白文字が削除されてしまうので、falseにすべき?

TextFieldParserを使う方法同士で速度を比較したサイトがある。
tabelog2001 - Qiita:【C#】TextFieldParserで100万行のCSVファイルに挑む。
上記サイトの実験例では下記の方法が最速であった。
・File.ReadAllLines.Lengthで、行数を取得
・MemoryStreamクラスで、File.ReadAllLinesの各要素(1行)単位で入力
・TextFieldParserクラスも、MemoryStreamに対応して、1行単位で処理
最も結果に影響したのは、反復処理にforeachを使用しないことであったようだ。
MemoryStreamクラスには、StreamReader.ReadLine()でも1行単位の入力が可能。
なお、Streamの派生クラスの中でもMemoryStreamクラスのようなシーク可能クラスはReadメソッド等で読取しただけではメモリ解放されないので注意。

タブ区切りの場合は、TextFieldParser.SetDelimitersにタブを指定すればよい。

スペース区切りの場合は、TextFieldParserクラスではなくString.Splitを使い、必要に応じてStringSplitOptions.RemoveEmptyEntriesを指定するとよい。

TextFieldParserクラスのコンストラクタ:TextFieldParser(TextReader reader)は、引数にTextReaderクラス(System.IO名前空間)を取ることができるので、TextReaderクラスの派生クラスである、StreamReaderクラス、StringReaderクラスも引数に取ることができる。

TextFieldParserクラスのコンストラクタで、StringReaderクラスを引数に取れば、文字列の解析にも利用できる。

<用語集>

字句解析器
・スキャナ(scanner)
・トークナイザ(tokenizer)

構文解析器
・パーサ(parser)

C# 1文字 読み込み ファイル 入力 サロゲート String 出力 文字化け

2023-11-21 12:44:17 | ソフトウェア開発
C言語のfgetcと同じように1文字ずつC#で読み込みたいときは、StreamReader.Readメソッドを使えばよく、戻り値がint型であることも同じである。ただし、改行コードも1文字ずつとなりCRLFが2文字となるため、処理に注意が必要である。

C#内部の文字コードはUTF-16だが、Windowsで扱うテキストファイルはShift-JISであったりUTF-8であったり(Windows10 May 2019 Update で、メモ帳の文字コード既定値がShift-JISからUTF-8に変わった)と、細かい処理をする際は注意が必要。Encoding.Defaultのコードページを調べてみると932(Shift-JIS)だったり、コマンドプロンプトの文字コードをchcpコマンドで調べてみると932(Shift-JIS)だったり、複雑だ。

C#内部の文字コードはUTF-16ということで、サロゲートの処理について気になったので、いろいろ調べてみた。

String型の内部はCharオブジェクトで構成され、各Charオブジェクトには各UTF-16の2Byteを格納している。
日経クロステック(xTECH):C#プログラムでサロゲート・ペアの動作を検証する(前編)

UTF-8では3Byte以上の文字もあるが、UTF-16で2Byteで表現できるものはCharオブジェクトからはみ出ることなく格納される。UTF-8やUTF-16では、Basic Multilingual Plane:基本多言語面を面00として表し、これ以外の面01-面10(16進数)をサロゲートとして表す。UTF-8では、面00は3Byteで表現可能で、面01-面10が4Byteとなる。

.NET Tips (VB.NET,C#...):文字列から1文字取得する、文字列内の文字を列挙する
.NET Tips (VB.NET,C#...):サロゲートペアや結合文字が含まれているか調べる

Char.IsSurrogateメソッドは引数がCharオブジェクトで、StreamReader.Readメソッドの戻り値のInt32オブジェクトとは異なる。StringBuilder.Appendメソッドは引数がInt32オブジェクトのものもあり、StreamReader.Readメソッドの戻り値のInt32オブジェクトがそのまま使えそう。よって、StreamReader.Readメソッドの戻り値をStringBuilder.Appendメソッドで連結した後、末尾のCharオブジェクトをChar.IsSurrogateメソッドで調べれば良い?

なお、サロゲートは、Windows のコマンドプロンプトが出力をサポートしておらず、そもそもコンソールバッファのひとマスが16bitしか確保してないらしい。
標準愚痴出力:Windows のコンソールと、Unicode のサロゲートペアとゼロ幅文字


C# 文字列 時刻 数値

2023-11-21 11:40:53 | ソフトウェア開発
本文作成のためただいま勉強中。

●アプリケーションを開始した実行ファイルのフォルダの取得
System.Windows.Forms.Application.StartupPath

●タブ区切りファイルを1行ずつ読み込み
StreamReader sr = new StreamReader(str, Encoding.GetEncoding("Shift_JIS"));
while (sr.EndOfStream == false)
{
string line = sr.ReadLine();
string[] fields = line.Split('\t');
}

●指定フォーマット文字列からDateTime構造体を取得
System.DateTime.ParseExact(str, "HH':'mm':'ss.f", System.Globalization.DateTimeFormatInfo.InvariantInfo, System.Globalization.DateTimeStyles.None);

<参考>

DOBON.NET:自分のアプリケーションの実行ファイルのパスを取得する
DOBON.NET:日時を表す文字列をDateTimeオブジェクトに変換する
DOBON.NET:日時(DateTimeオブジェクト)を文字列に変換する
DOBON.NET:日時、時間の計算をする
@IT:日時や時間間隔の加減算を行うには?
Yahoo!知恵袋:C#で折れ線グラフ描画に悩んでいます。