restofwaterimpのぎじゅつMemo

SIerに所属。企画から運用まで幅広くやってます。C#中心に書いてます。

明示的なインターフェイス

今読んでいる本で、「明示的なインターフェイス」という表現が出てきたが、馴染みがないので少し調べてみた。

C#実践開発手法 (マイクロソフト公式解説書)
Gary McLean Hall
日経BP
売り上げランキング: 373,052

明示的なインターフェイスの実装 - C# プログラミング ガイド | Microsoft Docs を参考に実施てみた。

 //一般的なインターフェイスの設定
    class Program
    {
        static void Main(string[] args)
        {
            SampleClass sc = new SampleClass();
            IControl ctrl = sc;
            ISurface srfc = sc;

            sc.Paint();
            ctrl.Paint();
            srfc.Paint();
            Console.ReadKey();

        }
    }

    interface IControl
    {
        void Paint();
    }

    interface ISurface
    {
        void Paint();
    }

    class SampleClass : IControl, ISurface
    {
        public void Paint()
        {
            Console.WriteLine("Paint method in SampleClass");
        }
    }

結果は想定通り、3回同じ出力がされた。 Paint method in SampleClass Paint method in SampleClass Paint method in SampleClass

明示的なインターフェースが役に立つのは、インターフェイスで実装しなければならないメソッドのシグネチャがクラスですでに存在していて、シグネチャの競合を回避したい場合。

 //明示的なインタフェースの設定
    class Program
    {
        static void Main(string[] args)
        {
            SampleClass sc = new SampleClass();
            IControl ctrl = sc;
            ISurface srfc = sc;

            sc.Paint();
            ctrl.Paint();
            srfc.Paint();
            Console.ReadKey();

        }
    }

    interface IControl
    {
        void Paint();
    }

    interface ISurface
    {
        void Paint();
    }

    class SampleClass : IControl, ISurface
    {
        public void Paint()
        {
            Console.WriteLine("Paint method in SampleClass");
        }

        void IControl.Paint() //インターフェイスを指定してメソッドを定義
        {
            Console.WriteLine("IContral.Paint");
        }

        void ISurface.Paint() //インターフェイスを指定してメソッドを定義
        {
            Console.WriteLine("ISurface.Paint");
        }
    }

結果は Paint method in SampleClass IContral.Paint ISurface.Paint と、クラスに定義したPaint()は暗黙的なメソッドを。インターフェイスを指定したメソッドはそれぞれ別のメソッドを呼び出している。

また、インターフェイスを継承したクラス内に、明示的な実装だけをしているなかで、インターフェイスではなく、クラスを参照型として 利用するとコンパイルエラーが発生する

 //明示的なインタフェースの設定
    class Program
    {
        static void Main(string[] args)
        {
            SampleClass sc = new SampleClass();
            IControl ctrl = sc;
            ISurface srfc = sc;

            Test test = new Test(sc, ctrl);

            //sc.Paint();
            ctrl.Paint();
            srfc.Paint();
            Console.ReadKey();

        }
    }

    interface IControl
    {
        void Paint();
    }

    interface ISurface
    {
        void Paint();
    }

    class SampleClass : IControl, ISurface
    {
        //public void Paint()
        //{
        //    Console.WriteLine("Paint method in SampleClass");
        //}

        void IControl.Paint() //インターフェイスを指定してメソッドを定義
        {
            Console.WriteLine("IContral.Paint");
        }

        void ISurface.Paint() //インターフェイスを指定してメソッドを定義
        {
            Console.WriteLine("ISurface.Paint");
        }
    }

    class Test
    {
        public Test(SampleClass sampleclass, IControl control)
        {
            sampleclass.Paint(); // これがエラー

            control.Paint();
        }
    }

エラー CS1061 'SampleClass' に 'Paint' の定義が含まれておらず、型 'SampleClass' の最初の引数を受け付けるアクセス可能な拡張メソッド 'Paint' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないかを確認してください。 ClearInterface C:\Users\user\source\repos\ClearInterface\ClearInterface\Program.cs 60 アクティブ

インターフェイスを継承するが、同じシグネチャのメソッドで処理の中身を変更したい場合に利用できるようである。(競合の回避)

オーバーライドしているわけではなく、インターフェイスで別々に定義していて、実装するクラスで意味合いをそれぞれ変えたい場合に 使えるのだろう。

使いどころがいまいちわからないが、実装クラスからどのインターフェイスを利用しているのかを明示的に使い分けたいときに利用するのだろう。

3章のデザインパターンに読書は続く・・

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

仕事で使いそうになったので、自身含めて、利用したことのないメンバーの為に整理する。

シリアル化、逆シリアル化の何を取るにしても、以下のSTEPで定義をすれば良さそう。

  1. シリアライズ、逆シリアライズの為の入出力ファイルの指定(外部ファイル)
  2. シリアライズ、逆シリアライズのデータ定義の指定(オブジェクト)
  3. 処理の実行

と思われる。

例えば、オブジェクトをシリアライズする場合

using(var writer = XMLWriter.Create("hoge.xml")){   // 1.シリアライズした出力先の設定
     var serializer = new XMLSerializer(hoge.GetType());   //  2.シリアライズする元データの設定(ここではhogeクラスに定義したデータ)
     serializer.Serialize(writer, hoge);                            //  3.シリアライズの実行
}

JSONデータを逆シリアライズし、オブジェクトに設定する場合

using(var stream = new FileStream("hoge.json", FileMode.Open, FileAccess.Read)){    // 1.逆シリアライズする元データの設定
      var serializer = new DataContractJsonSerializer(typeof(hoge[]));                //  2.逆シリアライズするオブジェクトの指定
      var hoge = serializer.ReadObject(stream) as hoge;                            // 3.逆シリアライズの実行
}

で行けそうかなと。

また、テキストでは、練習問題以外、JSONデータの並びと、 オブジェクトのプロパティ設定の定義順が同じだったので、XMLと同じように 型定義の順番が左右するかと思っていた。

しかし、練習問題を実施すると、そうではなさそう。ということで、ググってみると

stackoverrun.com

JSONには並び順の保証は内容で。 文字ならば、辞書のように、a,b,c・・・で昇順になっていそうです。 並び順を固定するには、連想配列などにして、実施するのがよさげかな?

【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章 正規表現。知らないと、暗号にしかみえないですからね〜

つづく