restofwaterimpのぎじゅつMemo

SIerに所属してます。企画から運用までやってます。現状、プロジェクトをスクラムで回したい!と試行錯誤中です

【C#】C#プログラミングのイディオム(12章)シリアル化、逆シリアル化(後半 JSON)

前回の続き 今回はJSON(JavaScript Object Notation)です。

データ転送時に利用される軽量なデータ記述言語の一つ。

JavaScript Object Notation - Wikipedia

JSONでデータを転記する場合は DataConstractJsonSerializerを利用します。

シリアル化のガイドライン

に記載がある通り、XMLへのシリアル化ではない場合はDataConstractを利用することになります。

自身が、JSONを利用したことがない(仕事で関わったことが残念ながらない・・・)ので 活用法やら、注意点やらがわからず。

これから調べるとします・・・。

ということで、つづく (次は14章の非同期へ。13章のEntity FrameWorkはとばす・・)

【C#】C#プログラミングのイディオム(12章)シリアル化、逆シリアル化(前半 XML)

前回の続き

restofwaterimp.hatenablog.com

11章でXMLファイルの操作ということで、XDocumentやXElementを利用した 操作が中心であった。

この章ではファイルの操作ではなく、ファイルのやりとりが中心の説明となっている。

シリアル化、逆シリアル化って?

f:id:restofwaterimp:20181011063653p:plain

シリアル化はオブジェクトの状態をネットワークを通じて転送できる形式やファイルに保存できる形式。 シリアル化したデータを元のオブジェクトに戻すことを逆シリアル化という。

この変換には System.Runtime.Serialization名前空間のDataContractSerializerを利用する。

docs.microsoft.com

オブジェクトを起点に、 WriteObjectがシリアル化、ReadObjectが逆シリアル化で、シリアルという特別な言葉ではなく、読み書きというメソッド名なので、 ファイルの読み書きと同じ感覚で利用できるのでは?と想定される。

書籍の例を元に記述(XMLファイル)

    class MainClass
    {
        public static void Main(string[] args)
        {
            var novel = new Novel
            {
                Author = "Jemus du Horgan",
                Title = "Continue to planet",
                Published = 1997,
            };

            var novels = new Novel[]{
                new Novel{
                    Author = "Jemus du Horgan",
                Title = "Continue to planet",
                Published = 1997,
                },
                new Novel{
                    Author = "H/G wells",
                Title = "Time machine",
                Published = 1895,
                }
            };

            var settings = new XmlWriterSettings
            {
                Encoding = new System.Text.UTF8Encoding(false),
                Indent = true,
                IndentChars = " ",
            };

            /*シリアル化*/
            using(var writer = XmlWriter.Create("novel.xml", settings)){
                var serializer = new DataContractSerializer(novels.GetType());
                serializer.WriteObject(writer, novels);
            }

            /*逆シリアル*/
            using(XmlReader reader = XmlReader.Create("novel.xml")){
                var serializer = new DataContractSerializer(typeof(Novel[]));
                var readnovels = serializer.ReadObject(reader) as Novel[];
                foreach(var readnovel in readnovels){
                    Console.WriteLine(readnovel);
                }
            }
        }


    }

    public class Novel{
        public string Title { get; set; }
        public string Author { get; set; }
        public int Published { get; set; }
        public override string ToString(){
            return string.Format("[Title={0}, Author={1}, Published=m{2}]", Title, Author, Published);
        }
    }
}

XMLWriterSettingでファイルの形式を決めないと、デフォルトのまま出力されます。 (改行なしのXMLファイルができます)

DataContractSerializerの代わりにXmlSerializerを利用することもできます。 本書ではXMLでデータを受け渡しをするにはこちらの方が良いと記述されております。

           using (var writer2 = XmlWriter.Create("novel.xml")){
                var serializer2 = new XmlSerializer(novel.GetType());
                serializer2.Serialize(writer2, book); //シリアル化するオブジェクト、書き込むための元データ
            }

/*シリアル化するもとのオブジェクト*/
    public class Novel{
        public string Title { get; set; }
        public string Author { get; set; }
        [XmlIgnore] //Ignoreをつけると、シリアル化対象外となる
        public int Published { get; set; }
        public override string ToString(){
            return string.Format("[Title={0}, Author={1}, Published=m{2}]", Title, Author, Published);
        }
    }

出力するとこんな感じに <?xml version="1.0" encoding="utf-8"?>Continue PlanetJames P Hogan

妖精名は宣言した時のプロパティ名になります。 もし、変更したい場合は [XmlElement(ElementName="xxxx")] を変更したいプロパティに記述するとできます。

XmlSerializer.Serialize Method (System.Xml.Serialization) | Microsoft Docs

DataContractSerializer
Serializes and deserializes an instance of a type into an XML stream or document using a supplied data contract. This class cannot be inherited.

XmlSerializer クラスの使用 | Microsoft Docs

それぞれのクラスの比較は下記のサイトに記載があったので、ご確認あれ。

DataContractSerializerを使って、オブジェクトのXMLシリアル化、逆シリアル化を行う - .NET Tips (VB.NET,C#...)

なんで、同じような機能があるのだろうと疑問に思い、検索してみたところ

シリアル化のガイドライン

と、MSからガイドラインがでていることを見つけた。

XMLSerializerを利用すると、Elementの詳細まで定義できるので、細かな設定が必要ならば、こちらの方が使いやすい。

とりとめないですが、つづく(JSONへ)

【C#】C#プログラミングのイディオム(11章)XMLの操作

前回のつづき

restofwaterimp.hatenablog.com

XMLJSONでデータ連係をすることもあるので、 その流れかなと。11章はXMLで12章はJSON

プレーンテキストからXMLJSONに変換し、他システムへ連係することがあるため イディオムに載っているのだろうと思われる章。

章の初めにはLINQ to XMLの話・・・と書いてありますが。 LINQ to Objectが理解できていれば、構文はほぼ同じなので、 そこまで苦労しないと思います。

XDocument のクエリと XElement のクエリ (C#) | Microsoft Docs

ファイルをloadするとき、XDocumentを利用するか、XElementを利用するかによって、 ルートを含んで全ての要素を取得するか否かが異なります。 そこには注意。

 var xdoc = XDocument.Load(@"/Users/xxxxx/Downloads/CSharpPhraseBook/Chapter11/Section02/novelists.xml");
            var xelements = xdoc.Root.Elements().OrderBy(x => (string)(x.Element("name").Attribute("kana")));
            foreach (var xnovelist in xelements)
            {
                XElement xname = xnovelist.Element("name");
                var birth = (DateTime)xnovelist.Element("birth");
                Console.WriteLine("{0} {1}", xname.Value, birth.ToShortDateString());

            }
            foreach (var xnovellist in xdoc.Root.Elements())
            {
                var name = xnovellist.Element("name");
                var works = xnovellist.Element("masterpieces")
                                      .Elements("title")
                                      .Select(x => x.Value);
                Console.WriteLine("{0} - {1}", name.Value, string.Join(",", works));


            }
            var filename = @"/Users/xxxx/Downloads/CSharpPhraseBookAnswer/Chap11/Exercise2/Sample.xml";

            var xdoc = XDocument.Load(filename);
/*xmlファイルを読み込みつつ、出力用のデータを作成。word要素に、属性kanji,yomiを設定したものをXElement属性で取得*/
            var lists = xdoc.Root.Elements().Select(x =>
                new XElement("word",
                            new XAttribute("kanji", x.Element("kanji").Value),
                            new XAttribute("yomi", x.Element("yomi").Value))
                                                   );

            var root = new XElement("difficaltkanji", lists);

            Console.WriteLine(root);
            root.Save(@"/Users/xxxx/Downloads/CSharpPhraseBookAnswer/Chap11/Exercise2/attribute.xml");

今回は内容が少ないですが、これで終了。 文字列操作なので、実際に文章を作成し、加工するトレーニングが良いのではと思い、実践します。

つづく

【C#】C#プログラミングのイディオム(10章)正規表現

前回の続き

restofwaterimp.hatenablog.com

10章は正規表現

Findメソッドでゴリゴリ書いてもいいけど、Regexを利用すれば簡単だし、わかりやすいよという章。

Macでコーディングだとエスケープシーケンスが ¥ではなく\なので、注意です。(バックスラッシュ)

指定した文字列が含まれているか

        /*静的メソッド*/
            var text = "private List<string> results = new List<string>();";
            bool isMatch = Regex.IsMatch(text, @"List<\w+>");
            if (isMatch)
            {
                Console.WriteLine("Find");
            }
                else{
                Console.WriteLine("Not Found");

                }

        /*インスタンスメソッド*/
            var regex = new Regex(@"List<\w+>");
            bool isMatch2 = regex.IsMatch(text);
            if (isMatch2)
            {
                Console.WriteLine("Find");
            }
            else
            {
                Console.WriteLine("Not Found");

            }

また、リテラルを逐語的リテラル@をつけた方がよいです。 つけないとエスケープシーケンスが¥¥¥とかになって、 「あれ?何書いていたんだっけ?」になりかねないので。

正規表現で利用できる文字自体は色々なサイトに公開されているので、そこをご参考あれ。

正規表現の構文

 どこと一致する?

前方一致 ^ 後方一致 $ 完全一致 ^ と $
を利用することで、一致条件を変更可能である。

c#自体では、Regex やReplace、Splitのメソッドなどを学ぶというのもありますが、 正規表現の読み方自体を覚えた方がいいという章でした。

つづく

【C#】C#プログラミングのイディオム(9章)ファイル操作

前回の続き

restofwaterimp.hatenablog.com

今回からは実践編に。 最初はファイルの操作。 アプリを構築するには外部ファイルとのやりとりがほぼある。 結構、例外とまでは行かないが、段階を踏んでファイルの読み書きをしないといけないが、 旧来のようにtry-catch-finallyではなくusingを利用しようという箇所もあった。

ファイル入力

var filePath = @"/Users/itoutomokazu/Documents/Testing/input.txt";

            /*昔はtry-finallyでリソース解放のため、Dispose()をしていた。今はusingで囲うみたい*/
            if(File.Exists(filePath)){
                //普通はEncoding.UTF8でいけるがshift_jisはGetEncodingを利用する
                using (var reader = new StreamReader(filePath, Encoding.GetEncoding("shift_jis"))) {
                    while(!reader.EndOfStream){
                        var line = reader.ReadLine();
                        Console.WriteLine(line);
                    }
                }
            }
        }

Fileのメソッドを利用する場合はReadAllLinesを利用することもできる。

docs.microsoft.com

ただ、読み切るまで、処理待ちが発生すること。 また、一度にメモリを利用するので、あまり大きなものは処理速度低下に繋がるようである。

.net 4.0以上では File.REadLinesを利用できるようです。

利用 StreamReader File.ReadAllLines File.ReadLines
戻り値 StreamReader string[] IEnumerable
取得タイミング StreamReader.ReadLine()で1行ごと ReadAllLinesでファイル全て 利用する時のみ
LINQで加工 不可 不可

/*最初の3行だけ取得*/
            var top3ines = File.ReadLines(filePath).Take(3);
            foreach(var line in top3ines)
            {
                Console.WriteLine(line);
            }

.netFrameworkが4.0以降を利用しているなら、積極的にReadLinesを利用したほうが良さそう。

ファイル出力

1行ごと出力する方法

            using(var writer = new StreamWriter(filewritePath)){
                writer.WriteLine("Get away , this floor");
                writer.WriteLine("Poo is a man.");
                writer.WriteLine("Where is you");
            }

            /*ファイルに値を追加をする場合には、appendを利用する。記載しないと上書き*/
            using (var writer = new StreamWriter(filewritePath, append:true))
            {
                writer.WriteLine("Get away , this floor");
                writer.WriteLine("Poo is a man.");
                writer.WriteLine("Where is you");
            }

docs.microsoft.com

複数行を一度の出力する場合、ファイルの読み込みと同じで、 File.WriteAllLinesを利用する。

既存のメソッドの利用では、ファイルの上書きまたは末尾に追記しかできない。 ファイルの先頭や間に差し込むことは自前で処理を用意する必要があります。 書籍には、例として、先頭に行を挿入する場合が記載されています。

            using (var stream = new FileStream(filewritePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)){
/* ファイルを途中でクローズせず、やりきること。閉じるとクリアされるから*/
                using (var reader = new StreamReader(stream)) 
                using (var writer = new StreamWriter(stream)){
                    string text = reader.ReadToEnd(); // ファイルの最初から最後まで読み,textに格納
                    stream.Position = 0;              // ポジションを先頭にする
                    writer.WriteLine("new line");     // 書き込み
                    writer.WriteLine("new Line !");
                    writer.Write("text");             // 読み込んでおいたtextを最後に書き込み
                }
            }

あるところに足すと言うより、一度退避して、再度追加したい内容とともに書き込みと言う感じです。 間に挟むとかなら、ターゲットとなる箇所までと、ターゲットとなる箇所以降で退避し、 間に挿入したい値を入れるとか。

ファイルの操作

ファイルの読み書きと同じように、ファイルを読み書きする前の確認もよく使います。 System.IO名前空間のFIle、FileInfo クラス が記載されている。

Fileを使うべきか・・・FileInfoを使うべきか・・・ということもテーマに上がっている。

書籍に書いてあるように、使っているのはFIleクラスを利用することがおおいな〜と思います。

ファイル入出力 (System.IO.File) - Programming/.NET Framework/ファイルシステム - 総武ソフトウェア推進所smdn.jp

docs.microsoft.com

docs.microsoft.com

見ると、Fileはstaticクラスなので、インスタンス化(new)が不要で、FileInfoはそうではない。 コードが少ない方、また例外処理の発生(ファイルがない・・・など)をうまく捕まえやすい方を使うのがいいかと。 いつもはFileクラス使ってますが。

ディレクトリの操作

ファイルの操作と同じくらい、ディレクトリの操作も使います。 業務アプリではファイルがあるか・・・というときに、そもそも該当のディレクトリがあるの? から始まりますので、フルパスをもらったら、Windows系なら¥でディレクトリを区切りながら フォルダチェックを行うことになります。

こちらもファイルと同じく、System.IO名前空間に DirectoryクラスとDirectoryInfoクラスがあります。

Fileと同様、基本はstatic なDirectoryクラスを利用します。

色々見て見ると、1つのフォルダやファイルに対し、複数操作を実施することがあるならば、 xxxInfoを。一つの操作のみで完結するならば、InfoをつけないFileやDirectoryを利用すると良いとのこと。 (ソース常に同じファイルをやディレクトリを何度も記載しなくて済むようにとのこと)

どちらもWindowsならdirやcd、moveなどのコマンド。 macならpwd,ls,cd,などのコマンドに相当することができます。

            /*ディレクトリに存在チェック*/
            if (Directory.Exists(@"/Users/xxxx/Documents/Testing"))
            {
                Console.WriteLine("フォルダある");
            }

            /*現在のディレクトリ取得*/
            var curdir = Directory.GetCurrentDirectory();
            Console.WriteLine(curdir);

            var di = new DirectoryInfo(@"/Users/xxx/Documents/");
            DirectoryInfo[] directories = di.GetDirectories().Where(x => x.Name.Length >=10);
            foreach(var dinfo in directories){
                Console.WriteLine(dinfo.FullName);
            }

            /*LINQで指定した条件に合致するディレクトリ一覧wお列挙*/
            var di2 = new DirectoryInfo(@"/Users/xxx/Documents/");
            var directories2 = di.EnumerateDirectories().Where(x => x.Name.Length >= 10);
            foreach (var dinfo in directories2)
            {
                Console.WriteLine(dinfo.FullName);
            }

パスの操作

取得した絶対パスやファイル名、等を組み合わせたり、バラしたりすることも Pathを利用すればできます。

            var path = @"/Users/itoutomokazu/Documents/Testing/output2.txt";
            var directoryName = Path.GetDirectoryName(path); // ファイルのディレクトリ
            var filename = Path.GetFileName(path); // ファイル名(拡張子付き)
            var extension = Path.GetExtension(path); //ファイルの拡張子
            var filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); //ファイル名(拡張子なし)
            var pathroot = Path.GetPathRoot(path); //ファイルがあるディレクトリのルート

            var path_combine = Path.Combine(directoryName, filename); //ディレクトリとファイル名を組み合わせて、絶対パスを作成(引数は何個でもいい)

アプリとDB以外でファイルでのやりとりが必要な場合に必要となってくる操作。 全部すぐには覚えられませんが、System.IOにFile、Directoryがあることを覚えておけば、 リファレンスをみてなんとか対処できるかな?

つぎは10章 正規表現。知らないと、暗号にしかみえないですからね〜

つづく

【C#】C#プログラミングのイディオム(8章)日付、時刻の操作

前回の続き

restofwaterimp.hatenablog.com

今回は日付、時刻。 よく使うのはDateTimeとstringの型変換と 差分を求めることかな。

私が使う時に、元号や曜日というのはあまり使わない。 もっぱら差分比較とか、n日前、n日後を使うことが多い。

     var dt1 = new DateTime(2016, 2, 4); //時分秒は0:00:00
            var dt2 = new DateTime(2019, 3, 2, 12, 22, 11);

            Console.WriteLine(dt1 +":"+ dt2);

            /*getしかプロパティに設定されていない*/
            Console.WriteLine(DateTime.Today); //時分秒は0:00:00
            Console.WriteLine(DateTime.Now);   //時分秒まで設定

            Console.WriteLine(DateTime.Today.DayOfWeek);


            /*日付を文字列に変換*/
            Console.WriteLine(dt1.ToString("d"));

            /*比較は演算子*/
            if(dt1 < dt2){
                Console.WriteLine("dt2の方が新しい日にち");
            }

            /*DateTime型は不変オブジェクトだが、初期設定は可能。差分は新たな変数へ*/
            var future = dt2 + new TimeSpan(2, 30, 0); //TimeSpanで時分秒を加減できる
            Console.WriteLine(future);

            var nextDay = dt2.AddDays(2); //Addxxxで加減したい単位を指定可能
            Console.WriteLine(nextDay);

日付型の表示変換はdocsに書いてある。

docs.microsoft.com

月末も昔、cobolとかやっていた時はうるう年考えて計算していたり、 最近でも、求めたい月の翌月の1日 - 1をして求めていたような。 そんなことしなくても

     var today = DateTime.Today;
            int day = DateTime.DaysInMonth(today.Year, today.Month);
            Console.WriteLine("今月は{0}日ある", day);

DaysInMonthメソッドを使えば、指定した年月の日数を求めてくれるので、月末日を求められます。

8.5 日時の計算(応用)

には、DayOfWeek列挙型を利用し、曜日から日付を求めるテクニックが記載されている。

DayOfWeekを利用することで、次のx曜日の日付を求めることができる。

       /*DayOfWee.xx で曜日ごとに値を持っている。列挙型で、日曜日から0,1,2,3,と始まる*/
            var days = (int)DayOfWeek.Monday - (int)(today.DayOfWeek);
            if (days <= 0) days += 7;
            Console.WriteLine(today.AddDays(days));

この章の最後に「プログラミングには唯一の正解はない」と描かれているが、その通りと思う。 機能として動けば、読みやすい・わかりやすいコードも、わかりにくいコードも同じ。 しかし、後々のことを考えると、わかりやすいコードでかつ、効率的、速度があるコードへ 徐々に書き換えた方がよい。

そこらへんはこの書籍だけでなく、他の良書に当たって、対応するのがよいかと。

次の章からは実践編 9章からは外部ファイルへのアクセス絡みに。

つづく

【C#】C#プログラミングのイディオム(7章)ディクショナリの操作

前回の話

restofwaterimp.hatenablog.com

基礎編もあと2章。次はディクショナリ。 業務アプリでは、データベースより取得した値や、内部でkey-valueで処理した方が良い時に利用しています。

ただ、あまり詳細なことを知らずに使っていた部分もあったので、書籍を読みながら振り返ります。

本章の内容 * Dictionaryの基本操作 * ディクショナリの応用 * サンプル(Abbreviationsクラス)

docs.microsoft.com

この章の前半はMicrosoftのリファレンスなどを参考するとわかりやすい。

            /* pre C# 5.0 */
            var flowerDict = new Dictionary<string, int>(){
                {"sunflower", 400},
                {"pansy", 300},
                {"tulip", 250},
                {"rose", 500},
                {"dahlia", 450},
            };

            /* after C# 6.0 */
            var flowerDict2 = new Dictionary<string, int>(){
                ["sunflower"] =  400,
                ["pansy"]= 300,
                ["tulip"]= 250,
                ["rose"]= 500,
                ["dahlia"]= 450,
            };

            // Add Dictionary Plus
            flowerDict.Add("himawari", 400);

            /*KeyとValueで分けないと、Dictionaryの値がそのまま出力される*/
            foreach (var s in flowerDict)
            {
                Console.WriteLine(s);
            }
            /*Keyとvalueに分ければ、それぞれの値を利用可能*/
            foreach (KeyValuePair<string, int> item in flowerDict)
            {
                Console.WriteLine("{0} = {1}", item.Key, item.Value);
            }

            flowerDict2["piti"] = 600;

            /*指定したキーの値がないものをそのまま利用すると、KeyNotFoundExceptionが発生*/
            Console.WriteLine(flowerDict["xx"]);

            /*回避するには、ContainsKeyを利用し存在チェックをするか、TryでExceptionを捕まえる*/
            /*指定したキーに該当する値があるか*/
            Console.WriteLine(flowerDict.ContainsKey("xx"));

通常のaddやremoveも利用しますが、Exceptionを発生させない方法を覚えておく。

タイプ keyの重複 格納する値
List 可能 なんでもOK
HashSet 不可 key
Dictionary 不可 key,value

Dictioonaryもkeyの重複はNGだが、valueのところをListやカスタムクラスを利用し、 keyに対するvalueを複数持たせることも可能で、 value に List型を指定すれば良い。

Listなら dict["xxx"].Add(value); と値を設定可能。

DictionaryのvalueにDictionaryを使う、多重構造もかけると思いますが、 記述すると複雑に・・・。 それよか、valueにクラスなどカスタマイズしたものを配置してコントロールした方が よさげです。

この章はざっと書いてありましたが、コーディングして身につけるのが一番の近道かと思います。

次は8章の時刻か・・・

つづく