2014年06月28日

再帰 指定フォルダの列挙 デリゲートを使う

指定フォルダ内にある全ファイル名の取得サンプル

特定のフォルダにあるファイルのリストはDirectoryInfoクラスのGetFilesメソッドで取得できる。
そのフォルダにあるサブフォルダのリストは同クラスのGetDirectoriesメソッドで取得できる。
GetFilesメソッドで取得したファイルを保持し、GetDirectoriesメソッドで取得したフォルダを起点として同処理を繰り返せば、サブフォルダのサブフォルダの・・・とすべて取得することができる。
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;

        static void Main(string[] args)
        {
            string basePath = @"C:\TestFolder";
            List<string> fileList = new List<string>();

            Search(basePath, fileList);
        }

        static void Search(string path,List<string> list)
        {
            DirectoryInfo dInfo = new DirectoryInfo(path);
            dInfo.GetFiles().ToList().ForEach(fi => list.Add(fi.FullName));
            dInfo.GetDirectories().ToList().ForEach(di => Search(di.FullName,list));
        }

このメソッドをデリゲートに置き換えたサンプル
処理の内容は同じですが、コードがその場にあるので可読性が上がるとはいえる。
他のメソッドからは呼べないので、複数個所で実行する必要がある場合は使えない。
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;

        static void Main(string[] args)
        {
            string basePath = @"C:\TestFolder";
            List<string> fileList = new List<string>();

            Action<string, List<string>> action = null;
            action = (path, list) =>
                {
                    DirectoryInfo dInfo = new DirectoryInfo(path);
                    dInfo.GetFiles().ToList().ForEach(fi => list.Add(fi.FullName));
                    dInfo.GetDirectories().ToList().ForEach(di => action(di.FullName, list));
                };

            action(basePath, fileList);
        }


※上記サンプルでは例外処理については省略しています。ファイル操作などは権限がないなどの理由によりアクセスしただけで例外がスローされる場合があります。
※再帰呼び出しは意外にコストが高い。不用意に呼び出すと帰ってこないことになりかねない。
※深すぎると散々待った後にStackOverflowExceptionが発生したりする。
posted by RR at 03:12 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2014年07月03日

Foreachでindex  LINQを使って

通常のforeach文ではindexが使えないのでFor文を使用するか別途カウンタを用意する。
            var list = new[] { 1,4,7,2,6,3,8,0,9,5};

            for (int count = 0; count < list.Length;++count )
            {
                var temp = string.Format("index:{0} / value :{1}", count, list[count]);
            }

            int index = 0;
            foreach(var value in list)
            {
                var temp = string.Format("index:{0} / value :{1}", index++, value);
            }


LINQのSelectやWhereなどにはシーケンスを射影するオーバーロードメソッドがある。
using System.Linq;

            var list = new[] { 1,4,7,2,6,3,8,0,9,5};
            // Select内でシーケンスを使う
            list.Select((value, index) => string.Format("index:{0} / value :{1}", index, value))
                .ToList().ForEach(Console.WriteLine);

            // Selectで匿名クラス型にシーケンスを乗せる(Foreach側にIndexを引き渡す)
            list.Select((value, index) => new { index, value })
                    .ToList().ForEach(a => Console.WriteLine("index:{0} / value :{1}", a.index, a.value));
            // Whereにもシーケンスあり
           list.Where((val, idx) => idx % 2 == 0).Select(val => val).ToList().ForEach(Console.WriteLine);
posted by RR at 16:07 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2014年07月13日

呼び出し元の情報取得

.NET4.5以前の方法

これまではStackFrameから情報を取得していました。
但し、難読化や最適化により想定外の値となってしまう場合があります。
using System.Diagnostics;
    class CallerSample
    {
        static void Main(string[] args)
        {
            WriteMessage("Test");
        }

        static void WriteMessage(string message) 
        {
            StackFrame frame = new StackFrame(1);
            string className = frame.GetMethod().ReflectedType.FullName;
            string method = frame.GetMethod().Name;
            
            Console.WriteLine("Message:{0} ,Class:{1} ,Method:{2}", message, className, method);
        }
    }

.NET 4.5以降の方法

新規に追加された下記の方法では、以下のように属性を指定することにより暗黙的にコンパイラにより文字列が付与されます。
using System.Diagnostics;
using System.Runtime.CompilerServices;
   class CallerSample
    {
        static void Main(string[] args)
        {
            WriteMessage("Test");
        }

        static void WriteMessage(string message,
        [CallerMemberName] string name = "",
        [CallerFilePath] string path = "",
        [CallerLineNumber] int line = 0)
        {
            Console.WriteLine("Message:{0} ,Name:{1} , path:{2} ,line:{3}",message,name,path,line);
        }
    }
posted by RR at 05:34 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2014年07月31日

CodeDom 動的プログラミング


CodeDom

CodeDom (Code Document Object Model)を使えば動的にコードを作れたりします。
生成したコードはメモリ上に展開することもファイルに出力することも可能。
型付DataRowのようにテーブル構成に相当するデータ保持用のPOCOやDTOなどを大量に生成する場合などに有効。

サンプルとして以下のコードを作ります。

namespace SampleNameSpace
{
    public class Person
    {
        private string _firstName;
        private string _lastName;

        public string FirstName
        {
            get { return _firstName; }
            set { _firstName = value; }
        }

        public string LastName
        {
            get { return _lastName; }
            set { _lastName = value; }
        }

        public string FullName
        {
            get { return _firstName + _lastName; }
        }

        public Person()
        {

        }
    }
}

CodeDomの生成

先ずはCodDomを作るコードです。コメントやクラス名から処理が簡単に追えると思います。
using System.CodeDom;
namespace Sample
{
    class CodeDomSample
    {
        private static CodeNamespace CreateCodeDom()
        {
            // 名前空間
            var nameSpace = new CodeNamespace("SampleNameSpace");
            // クラス
            var classPerson = new CodeTypeDeclaration("Person");

            nameSpace.Types.Add(classPerson);

            // 名前用フィールド
            var fieldFirstName = new CodeMemberField(typeof(string), "_firstName")
            {
                Attributes = MemberAttributes.Private | MemberAttributes.Final
            };
            // 名前用プロパティ
            var propertyFirstName = new CodeMemberProperty()
            {
                Name = "FirstName",
                Attributes = MemberAttributes.Public | MemberAttributes.Final,
                Type = new CodeTypeReference(typeof(string))
            };

            // 名前用プロパティの参照先
            var firstNameFieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_firstName");
            // 名前用プロパティのGetter
            propertyFirstName.GetStatements.Add(new CodeMethodReturnStatement(firstNameFieldReference));
            // 名前用プロパティのSetter
            propertyFirstName.SetStatements.Add(new CodeAssignStatement(firstNameFieldReference, new CodePropertySetValueReferenceExpression()));

            classPerson.Members.Add(propertyFirstName);

            // 苗字用
            var fieldLastName = new CodeMemberField(typeof(string), "_lastName")
            {
                Attributes = MemberAttributes.Private | MemberAttributes.Final
            };

            var propertyLastName = new CodeMemberProperty()
            {
                Name = "LastName",
                Attributes = MemberAttributes.Public | MemberAttributes.Final,
                Type = new CodeTypeReference(typeof(string))
            };

            var lastNameFieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_lastName");

            propertyLastName.GetStatements.Add(new CodeMethodReturnStatement(lastNameFieldReference));

            propertyLastName.SetStatements.Add(new CodeAssignStatement(lastNameFieldReference, new CodePropertySetValueReferenceExpression()));

            classPerson.Members.Add(propertyLastName);

            // フルネーム用プロパティ
            var propertyFullName = new CodeMemberProperty()
            {
                Name = "FullName",
                Attributes = MemberAttributes.Public,
                Type = new CodeTypeReference(typeof(string))
            };

            // フルネーム用Getter
            propertyFullName.GetStatements.Add(new CodeMethodReturnStatement(
                    new CodeBinaryOperatorExpression(firstNameFieldReference, CodeBinaryOperatorType.Add, lastNameFieldReference)
                    ));

            classPerson.Members.Add(propertyFullName);
            // コンストラクタ
            var constructor = new CodeConstructor()
            {
                Attributes = MemberAttributes.Public
            };

            classPerson.Members.Add(constructor);

            return nameSpace;
        }
    }
}
次に、これを使ってファイルに出力します。
using Microsoft.CSharp;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
namespace Sample
{
    class CodeDomSample
    {
        static void Main(string[] args)
        {
            var provider = new CSharpCodeProvider();

            using (var write = new StreamWriter(@"CodeDomSample.cs"))
           {
               using(var tw = new IndentedTextWriter(write, "    "))
               {
                   provider.GenerateCodeFromNamespace(CreateCodeDom(), tw, new CodeGeneratorOptions());
               }
           }
        }
    }
}

出力結果

上記コードより出力されたファイルの中身がこちら。
namespace SampleNameSpace {
    
    
    public class Person {
        
        public Person() {
        }
        
        public string FirstName {
            get {
                return this._firstName;
            }
            set {
                this._firstName = value;
            }
        }
        
        public string LastName {
            get {
                return this._lastName;
            }
            set {
                this._lastName = value;
            }
        }
        
        public virtual string FullName {
            get {
                return (this._firstName + this._lastName);
            }
        }
    }
}

posted by RR at 18:13 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2014年08月26日

DTO と POCO と Entity の違い

DTO,POCO,Entity


DTOやPOCOやEntityは何が違うのか。何度も話題となる古い問題
「どれも同じようなもん」という人もいれば「全然役割が違うんだよ」という人もいる。
解説している人や本によっても内容が異なるので正解はよくわからないのだけれど、こんな感じでしょうか。

DTO

Data Transfer Object
通常アプリケーションはUI層・ビジネスロジック層・データアクセス層のように、その責務によりいくつかの層に分けられる。これらの層間のデータのやり取りに使うための入れ物が起源。
最も何も機能を持たない。ほぼアクセッサのみで成る。

public SampleDto
{
    public int ID { get;set;}
    public string Name{get;set;}
    public string Address{get;set;}
}


POCO

Plain Old CLR Object の略記とも言われる。
ファウラー御大が2000年に言い始めたPOJO(Plain Old Java Object)の.NET版。
こういった入れ物を特定のフレームワーク上で定義する場合は特定のクラスなどを継承する場合が多く、その親となるクラスにはデータ永続化など何らかの機能が実装されてたりする。このような特定のアーキテクチャに依存しないという意味でPlainなもの。
上記DTOに近いが、永続化ではない若干の処理、例えばDataBind用のプロパティに対する属性だとか、自フィールド値に対する検証処理などが付く場合が多い。

public SamplePoco
{
    [Required]
    public int ID { get;set;}
    [MaxLength(30)]
    public string Name{get;set;}
    [NonRequired]
    public string Address{get;set;}

    public bool Validate(){ return ID > 0;}
}

Entity

データベースのテーブルにあるカラムに相当する型というイメージ。
O/R Mapperで行データをマップする先のオブジェクトの型。
入れ物系定義でデータベースに近いところ。
posted by RR at 22:06 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2015年01月15日

プロパティ名を文字列として取得

例えば、日付を表すDateTime構造体にはDateというプロパティがあります。

これを"Date"という文字列として取得する方法の紹介。

MVVMが流行りだしてViewModelのPropertyChangedの実装がメンドクサイ頃に話題となった問題。

普通は取れないんですが、世の中には頭のいい人がいるもんです。

元ネタはこちら 

http://blogs.msdn.com/b/csharpfaq/archive/2010/03/11/how-can-i-get-objects-and-property-values-from-expression-trees.aspx

プロパティ名を文字列として取得

using System.Linq.Expressions;

    static class Util
    {
        public static string GetName<T>(Expression<Func<T>> e)
        {
            var member = (MemberExpression)e.Body;
            return member.Member.Name;
        }
    }


使う方

       static void Main(string[] args)
        {
            DateTime dt = DateTime.Now;

            // ここで戻値のnameが"Day"となる
            string name = Util.GetName(() => dt.Day);
        }

ん、すばらしい。


追記
C#6.0ではnameofという演算子が登場するらしい。こんな感じ。
  string name = nameof(DateTime.Now);
posted by RR at 22:42 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2015年01月23日

文字色をそのままにソースコードをエクセルに貼り付け

C#のソースコードである XXX.csファイルをVisualStudioのエディタでみると、予約語やコメントが色分けされているので読みやすいです。

これをコピペしてエクセルに張り付けると普通の黒一色の文字となってしまい、読みずらい。

根強いエクセル文化圏でコーディング規約や実装手順書などの資料を作成する場合は、ちょっと残念な資料になりそうなのですが、ちゃんとVisualStudioのエディタでの色設定を保持して貼り付ける方法がありました。

(ちょっと検索するとすぐ出てくるのですが、そういう発想自体がありませんでした。最近知りました。)

いくつか方法があるのですが、そのうち2つを紹介します。

ワードを使う

Visual Studioのソースエディタで開いているソースコードをCtr-C等でコピーし、一度ワードに貼り付けます。この場合、文字色の設定も同じように張り付きます。
次に、貼り付けたワード側の文字をCtr-C等で再度コピーし直し、それをエクセルに貼り付けます。

一段階手間が増えますが、結構簡単なので、量が少ない場合はお勧めです。

プラグインを使う

Microsoft社謹製のアドオンがあります。無料です。
Productivity Power Toolsといい、以下からダウンロードできます。

https://visualstudiogallery.msdn.microsoft.com/d0d33361-18e2-46c0-8ff2-4adea1e34fef/

引用します。

HTML Copy.
This extension provides support for the HTML Clipboard format when cutting or copying code from the editor. This means that you’ll no longer have to go fix up the formatting of your code when you paste it into a TFS bug form or any other HTML based control. It is possible to modify the html format which gets copied to the clipboard and with the release you now have the ability to customize that to suite your needs. Simply go to Tools | Options | Productivity Power Tools | HTML Copy:


つまり、これをインスコしてオプションを設定すればワードなんぞ介さずとも直接エクセルにコピペできますよってことらしいです。


※自社の環境(VS2012にOffice2013では直接エクセルは黒一色になりましたが、ワード経由だと色の設定も含めて上手く行きました。
 自宅の環境(VS2013にOpenOfficeのCal)だと直接Calに貼り付けても色の設定も再現されました。
posted by RR at 00:39 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2015年04月10日

クラスと構造体のどちらを使うか

議論の前提

たとえば、身長と体重の値を持ち、BMIを算出するメソッドを持つクラスを定義するとする。
    public class Person
    {
        public double Height { get; set; }
        public double Weight { get; set; }

        public double GetBMI()
        {
            if (Height == 0 || Weight == 0) 
                throw new InvalidOperationException();

            return Weight / (Height * Height);
        }
    }
これをクラスではなく構造体として定義すると以下のようになる。
    public struct Person
    {
        public double Height { get; set; }
        public double Weight { get; set; }

        public double GetBMI()
        {
            if (Height == 0 || Weight == 0) 
                throw new InvalidOperationException();

            return Weight / (Height * Height);
        }
    }

違いはclassstructに変更しただけ。
C#ではクラスも構造体も状態(フィールド)と振舞い(メソッド)とを持てるので、どちらでも実装可能な場合が多い。そのため、どちらで実装すべきかが議論となる場合がある。

構造体の特徴

値型
・new演算子を使わなくてもインスタンス化される。
・System.ValueTypeを直接継承し、自身は継承元にもなれない(但し、インタフェースを実現はできる)
・デフォルトコンストラクタ(引数なしのコンストラクタ)を定義できない。
・フィールド初期化子による初期化は定義できない。
※コンパイルされたアセンブリをみるとデフォルトコンストラクタはコンパイラにより暗黙的に生成されDefault Zeroingという特殊な(効率的な)初期化処理が行われる。
 フィールド初期化はコンパイラによりコンストラクタに移動させられるのだが、移動先は別用途で使用されているため定義できない。

効率(パフォーマンス)の問題

MSDNのプログラミングガイドから引用します。

構造体は軽量オブジェクトを表すのに適しています。参照型の機能が必要ないのなら、小さいクラスとして実装するよりは構造体として定義した方が、システムにより効率的に処理されることが期待できます。

実際に計測してみると(かなりハイスペックなマシンでデバッグビルド版で計測)、上のサンプルコードにあるクラスと構造体とを10万個づつインスタンス化してもいづれも2msと差はなし。100万回でクラスが約100msに対し構造体が30msと約3倍の効率の良さは伺える。
これらを「参照型の機能」として、つまりBoxingされるようObject型にキャストする処理を介在させた場合、クラスも構造体も120msぐらいであった。

これって、実際の使用条件等を考えるとほとんど構造体を使う理由がないように思えます。

参考 System.IO名前空間

.NET Framework4.5 のクラスライブラリでSystem.IO名前空間に定義されている型を数えると、クラスが39に対し、構造体はWaitForChangedResultの1つのみ。そのメンバは以下のとおり。
public struct WaitForChangedResult
{
    public WatcherChangeTypes ChangeType { get; set; }
    public string Name { get; set; }
    public string OldName { get; set; }
    public bool TimedOut { get; set; }   
}

つまり、このぐらいのメンバ数がクラスでなくて構造体として定義すべきかを考える目安ではないかと思われる。
一般的には構造体のサイズは16バイト以下だと効率が良いとされる。それ以上だと値をコピーするよりポインタ(参照)をコピーする方がコストが低い場合が多いらしい。但し場合によりけりだそうです。


構造体使用時の注意点

変数に代入する場合、クラスだと参照(つまりポインタ)がコピーされるのに対し、構造体(を含む値型)では、値そのものがコピーされる。
        var person1 = new Person { Height = 1, Weight = 75 };
        var person2 = person1;
        person1.Height = 2;
        // ここで、Personがクラスとして定義されていれば2、構造体なら1のままとなる
        var heightC = person2.Height; 

構造体を使う必要があるケース


以上より「物凄く大量にインスタンス化され」「メンバはあまり多くなく」「参照型にキャストされることはなく」かつ「ミリ秒単位での効率化を勘案する必要がある」という極く限られた場合を除き、構造体を使う場所は無いのだが、そういう縛りがなくても構造体を使う必要な箇所はいくつかある。

たとえば、Win32APIなどのようにネイティブのDLLを.NET側から呼び出す場合、引数として構造体を渡す必要がある場合がある。
マトメ:多分、使いどころはそのぐらい。
posted by RR at 16:55 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2015年04月26日

コメント 普通のコメントとTODOコメントとXMLドキュメントコメントと

コメント
// 〜〜
/* 〜〜 */
// 一行コメント

/*
 * 複数行コメント
 */
TODOコメント
// TODO・・・

TODOから始まるコメントを付けておくと、一覧で確認することができる。
VisualStudioのメニュー「表示」⇒「タスク一覧」でタスク一覧ウィンドウを表示
コンボボックスで「コメント」を選択するとリスト表示される。


XMLドキュメントコメント
クラスやメソッドなどの説明を以下の形式でソースコードに記述したもの
/// 〜〜
/** <タグ>〜〜</タグ> */
    /**
     * サンプルクラス
     * XMLコメント説明用
     */
    public class SampleClass
    {
        /// 
        /// コンストラクタ
        /// 
        /// 
        /// インスタンスを初期化します。
        /// 
        public SampleClass()
        {
        }
    }
XMLドキュメントとして推奨されるタグ
http://msdn.microsoft.com/ja-jp/library/5ast78ax.aspx

XMLドキュメントの出力設定
「プロジェクト」の「プロパティ設定」画面の「ビルド」タブに「XMLドキュメントファイル」という項目があります。
このチェックボックスにチェックをいれ、出力ファイルを指定すると(アセンブリ名で拡張子がXML)コンパイル時にファイルが生成されます。
ライブラリと同一フォルダに配置するとアセンブリ参照でもインテリセンスで説明が表示されるようになります。

API仕様書の作成
このファイルに対してツールを使用することによりAPI仕様書(DOCファイル)を生成することができます。
Doxygen
http://www.stack.nl/~dimitri/doxygen/
Sandcastle
http://msdn.microsoft.com/en-us/vstudio/bb608422.aspx
NDoc3
http://sourceforge.net/projects/ndoc3/
posted by RR at 23:32 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2015年04月27日

C#型(組み込み型)と.NET Frame型

プログラミング言語に組み込み型(Build-in Types)というものがあります。

例えば、.NET Framework型でSystem.Int32構造体int,System.Stringクラスstringというようなものです。

これらは.NET Framework型のエイリアス(別名)なので、意味は全く同じ、コンパイルして出力されるILも全く同等であると勉強した記憶が微かにあります。

しかし、一つ例外がありました。列挙体の定義です。結構有名な話みたいで検索すると沢山でてきます。
    // これは問題なし
    enum TrafficLight : int
    {
        Red,
        Yellow,
        Blue
    }
    // これはコンパイルが通らない
    enum TrafficLight : System.Int32
    {
        Red,
        Yellow,
        Blue
    }

いまのところ、差異はこのぐらいのようですが、次版のC#ではもう一つ増えて、クラス名やメソッド名などを文字列として取得するnameof演算子では組み込み型が使えないとのこと。

    string str1 = nameof(Int32);
    //string str2 = nameof(int); 
posted by RR at 20:56 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2015年10月15日

メソッドから複数の値を返すいくつかの方法

メソッドから異なる型や複数の値を返却したい場合がある。
たとえば、処理結果と処理成否とを返したい場合など。

下のメソッドはint型の値を二つとり、加算結果を戻値として返却する。
       public int Add(int x, int y)
       {
           return x + y;
       }
これは数値が大きい場合とオーバーフローして不正な値が返却される不具合がある。
checkedを付与すると演算処理時にOverflowExceptionの有無でオーバーフロー発生有無を判断できるが、 この加算処理結果と処理成否との2種類の値をどう返却するかが問題となる。
        public int Add(int x ,int y)
        {
            try
            {
                return checked(x + y);
            }
            catch (OverflowException)
            {
                // どうするか?
            }
        }

処理成否を例外で返却
メソッド内でOverflowExceptionを処理せずに、そのまま例外を呼び元側に伝搬させる方法。
呼び元側で都度Try-Catchを記述するのがしんどいという問題は残る。
       public int Add(int x ,int y)
        {
            return checked(x + y);
        }

結果も成否も戻値で
演算結果がint型で処理成否がbool型だとしてもobjectなら返却できるので文法上は可能。
呼び元で返却値の型チェックの処理が必要になるのでかなり煩雑。大概は代案があるはず。
       public object Add(int x ,int y)
        {
            try
            {
                return checked(x + y);
            }
            catch (OverflowException)
            {
                return false;
            }
        }

特殊な値とする
例えば、必ず処理結果がプラスの値と条件付けられるような場合にマイナス値を返すとか。
若しくは失敗時にnull値とするなど。特殊値が取れるならかなりマシな解決法。
        public int Add(int x ,int y)
        {
            try
            {
                return checked(x + y);
            }
            catch (OverflowException)
            {
                return -1;
            }
        }

        public Nullable<int> Add(int x ,int y)
        {
            try
            {
                return checked(x + y);
            }
            catch (OverflowException)
            {
                return null;
            }
        }

算術結果と成否とを返却する
返却用に処理結果と処理成否とをメンバとするクラスや構造体を定義し、それを返却する。
        public RetValue Add(int x ,int y)
        {
            try
            {
                return new RetValue { Value = checked(x + y), HasFail = false };
            }
            catch (OverflowException)
            {
                return new RetValue { Value = 0, HasFail = true }; ;
            }
        }

    struct RetValue
    {
        public int Value { get; set; }
        public bool HasFail { get; set; }
    }

こうしたクラス定義(構造体定義)が増えるのはウザったいが、4.0以降ならDynamicが使える。
匿名クラスにすれば余計な定義が不要。
        public dynamic Add(int x ,int y)
        {
            try
            {
                return new { Value = checked(x + y), HasFail = false };
            }
            catch (OverflowException)
            {
                return new { Value = 0, HasFail = true }; ;
            }
        }

返却用の型を定義する方法の場合、このような公開プロパティだけがメンバーとして定義される型が沢山必要になるし、Dynamicの場合はインテリセンスが使えずコーディングミスが実行してみないと分からないという問題がありました。
C#7以降ならTuppleを使うことでこの両方の問題が解決されます。便利になりました。
        public (int Value,bool HasFail) Add(int x,int y)
        {
            try
            {
                return (checked(x + y), false);
            }
            catch(OverflowException)
            {
                return (0, true);
            }
        }

成否を戻値で返し、演算結果はOUTパラメータで渡す
戻値は1つだが、パラメータはいくつでも定義が可能である。OUTパラメータ(若しくはrefパラメータ)を使うことにより、こちらで値をいくつでも返却できる。今回の例の場合なら、多分これが最適解。
        public bool TryAdd(out int sum, int x ,int y)
        {
            try
            {
                sum = checked(x + y);

                return true;
            }
            catch (OverflowException)
            {
                sum = 0;

                return false;
            }
        }

フィールドとかグローバル変数とかコンテキストとかに結果を置いとく
大概はリファクタ対象だが、たまにこういう実装が正しい場合があったりする。

        public int Sum { get; set; }
        public bool HasError { get; set; }

        public void Add(int x, int y)
        {
            try
            {
                Sum = checked(x + y);
                HasError = false;
            }
            catch (OverflowException)
            {
                HasError = true;
            }
        }
posted by RR at 00:01 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2015年11月10日

祝日の求め方

書き直しました(2017/03/12)。以下のページです。

祝日の求め方 その2



日本では祝日は「国民の祝日に関する法律」という法律で決まっています。

この法律によると、2015年は15日で、2016年は「山の日」が追加されて16日あります。

内閣府にあるこのページに詳しいです。

ただし、これが全てではなくて慶弔事等で特別法が制定されて、例えば1993年のみ6月9日が休日になったりします。

祝日法の改正や特別法の制定は今後どうなるのかはわからんので、アルゴリズムで祝日を算出するのは理論上は無理です。(このような不確定部を除いた算出法はネット上にいくつかサンプルがあります)

業務アプリケーションを構築する場合、このような祝日の他に年末年始休暇や創立記念日などの休みも必要となることからデータベース等に祝日マスタ(または稼働日マスタ)を保持しておくことが一般的なようです。

別解として祝日をウェブサービスから取得するという方法もあります。

GoogleAPIから取得する方法が一般的なようですが、こちらでも同様のサービスがありました。しばらく更新されてないようですがとりあえずちゃんと動くようです。

C#からはこんな風に取得できます。
クエリ文字を変えると他にも取得できます。(詳細は本家ページにあります)

using System.Net;
using System.IO;

        public static bool IsHoliday(DateTime date)
        {
            using (var wc = new WebClient())
            {
                using (var stream = wc.OpenRead("http://s-proj.com/utils/checkHoliday.php?kind=h&date=" + date.ToString("yyyyMMdd")))
                {
                    using (var reader = new StreamReader(stream))
                    {
                        var ret = reader.ReadToEnd();

                        if (ret == "holiday") return true;
                        else if (ret == "else") return false;
                        else throw new ApplicationException();
                    }
                }
            }
        }
posted by RR at 00:48 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2015年12月09日

アプリケーションの実行に管理者権限を要求する方法

アプリケーションの実行に管理者権限を要求する方法


AddNewItemDialog.png

新しい項目の追加メニューでアプリケーションマニフェストファイルを追加する。
拡張子がmanifestのファイルが追加されるのでそれを編集する。

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <!-- UAC マニフェスト オプション
             Windows のユーザー アカウント制御のレベルを変更するには、
             requestedExecutionLevel ノードを以下のいずれかで置換します。

        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />

            requestedExecutionLevel 要素を指定すると、ファイルおよびレジストリの仮想化が無効にされます。
            アプリケーションが下位互換性を保つためにこの仮想化を要求する場合、この要素を
            削除します。
        -->
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </requestedPrivileges>
    </security>
  </trustInfo>

うまく文字色が変ってませんが、7行目から18行目まではコメントです。
目的の箇所は19行目の定義。ここでlevelasInvokerからrequireAdministratorに変えます。
これだけ。

デバック実行の注意


この設定後にデバッグ実行すると「管理者権限がないので実行できない」と怒られる場合があります。
この場合は、一度VisualStudioを終了し、再度VisualStudio自身を管理者権限で実行します。
メニューを右クリックすると「管理者として実行」があるのでそれを選択する。
若しくはショートカットの設定を弄る。

常に管理者権限で実行させるにはレジストリを変更します。
posted by RR at 00:38 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2016年03月14日

データの永続化 設定をどう保持するか

概要

データや設定などをプロセス終了後にも使用できるように、どう保存するかという問題
データベースなど他のプロセスやサービスを介する場合もあるが、ここではファイルへの保存を対象としてます

考慮事項

以下のような事項からどういったどう保存するのか検討していくことになる
  • 対象は何か
  • どのくらいの量か
  • 誰が使うか(自プログラムのみか)
  • ファイルの編集を許可するのか
  • 暗号化等の要否
  • 等々

どこに保存するか

保存形式により一意に決まるものもあるが、大体以下の場所が候補となる
  • アセンブリ内
  • 実行環境
  • 特殊フォルダ
  • 任意のフォルダ
以下はアプリケーションデータフォルダのパスを取得するサンプルコード
引数の列挙値とそれにより取得されるパスについてはMSDNを参照のこと

    string path = System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));

保存形式

INIファイル

拡張子がiniとなるテキストファイルでセクションとキーと値とから成る
大昔からあるが.NETでは非推奨で共通ライブラリにAPIもない
既存アプリや他アプリとの関係などのシガラミで使われることはある
using System.Runtime.InteropServices;
using System.Text;

    public class IniFileUtil
    {
        private string _path;

        [DllImport("kernel32")]
        private static extern long WritePrivateProfileString(string section,
            string key, string val, string filePath);
        [DllImport("kernel32")]
        private static extern int GetPrivateProfileString(string section,
                 string key, string def, StringBuilder retVal,
            int size, string filePath);

        // 引数のパスはファイルパス
        public IniFileUtil(string path)
        {
            _path = path;
        }

        public void IniWriteValue(string Section, string Key, string Value)
        {
            WritePrivateProfileString(Section, Key, Value, this._path);
        }

        public string IniReadValue(string Section, string Key)
        {
            StringBuilder sb = new StringBuilder(255);
            int i = GetPrivateProfileString(Section, Key, "", sb, 255, this._path);

            return sb.ToString();
        }
    }

レジストリ

レジストリとは、WindowsOSで使用される特殊なデータファイル
OS自体の設定もあるので編集に失敗するとWindows自体起動しなくなったりする
他のアプリ等に破損させられる可能性もあるとか肥大化しやすいとかデメリットあり
レジストリエディタを知らないと編集されずらいという利点もある
using Microsoft.Win32;
public class RegistryUtil
    {
        private string _path;

        // 引数のパスはレジストリのパス
        public RegistryUtil(string path)
        {
            _path = path;
        }

        public void Write(string key, object value)
        {
            using (var regkey = Registry.CurrentUser.CreateSubKey(_path))
            {
                regkey.SetValue(key, value);
            }
        }

        public object Read(string key)
        {
            using (var regkey = Registry.CurrentUser.OpenSubKey(_path, false))
            {
                if (regkey == null) return null;

                return regkey.GetValue(key);
            }
        }
    }

構成ファイルその1

構成ファイルとは、実行ファイルと同じフォルダに配置され、confingという拡張子が付くXMLファイル
プロジェクトに追加するとApp.configといったファイル名で登録されるが、ビルドすると実行環境には[ApplicationName].exe.configとリネームされ配置される
取得・編集用のメソッド等がいくつも提供されているが以下はそのうちの一つ
using System.Configuration;

    public class AppConfigUtil
    {
        public string Read(string key)
        {
            // プロセス起動時の値を保持しているので、編集処理がある場合は値を再取得する必要あり
            // ConfigurationManager.RefreshSection("appSettings");

            if (ConfigurationManager.AppSettings.AllKeys.Contains(key))
            {
                return ConfigurationManager.AppSettings[key];
            }

            return null;
        }

        public void Write(string key, string value)
        {
            var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

            if(config.AppSettings.Settings.AllKeys.Contains(key))
            {
                config.AppSettings.Settings[key].Value = value;
            }
            else
            {
                config.AppSettings.Settings.Add(key, value);
            }

            config.Save();
        }
    }

※デバック実行の場合、実行環境にある編集したファイルがビルド毎に上書きされるので要注意

構成ファイルその2

プロジェクトファイルのプロパティを選択し、プロパティ画面の「設定」タブに名前・型・スコープ・値を設定することができます
設定は構成ファイルに保持され、アクセス用のコードが自動生成されます

以下は名前:SampleString、型:String、スコープ:ユーザーで作成した設定の取得・変更用サンプル
    public class SettingUtil
    {
        public string GetSampleString()
        {
            return Properties.Settings.Default.SampleString;
        }

        public void SetSampleString(string value)
        {
            Properties.Settings.Default.SampleString = value;
            Properties.Settings.Default.Save();
        }
    }

リソースファイル

プロジェクトに「新しい項目の追加」⇒「リソースファイル」で拡張子resxというファイルが追加されます
プロパティ画面の設定タブと同様に設定を追加し取得することが可能です
値はハードコードされアセンブリ内に保持されます。編集保存はできません
    public class ResourceFileUtil
    {
        public string GetSampleString()
        {
            return Properties.Resources.SampleString;
        }
    }

シリアライズ

オブジェクトをそのままファイルに保存できるし復元できるので便利
使用するシリアライザーによってはファイルサイズが大きくなる

以下はXML形式でシリアライズするサンプル
using System.IO;
using System.Xml.Serialization;
    public class XmlSerializeUtil
    {
        private string _path;

        // 引数のパスは保存ファイル
        public XmlSerializeUtil(string path)
        {
            _path = path;
        }

        // 復元(デシリアライズ)する際に型を指定する
        public T Read<T>()
        {
            var serializer = new XmlSerializer(typeof(T));

            using (var reader = new StreamReader(_path))
            {
                return (T)serializer.Deserialize(reader);
            }
        }

        public void Write<T>(T obj)
        {
            var serializer = new XmlSerializer(typeof(T));

            using (var writer = new StreamWriter(_path,false,Encoding.UTF8))
            {
                serializer.Serialize(writer, obj);
            }
        }
    }


テキストファイル

場合よってはプレーンのテキストファイルが一番だったりする
    public class TextFileUtil
    {
        private string _path;

        // 引数のパスは保存ファイル
        public TextFileUtil(string path)
        {
            _path = path;
        }

        public string Read()
        {
            return File.Exists(_path) ? File.ReadAllText(_path) : string.Empty;
        }

        public void Write(string value)
        {
            File.WriteAllText(_path, value);
        }
    }

SQLite

ファイルへのアクセスを標準SQLで実行できる無料のライブラリ
保存対象のデータが大量にある場合などで有効
別途記載(予定)

その他


・複数のプロセスが同時に編集する可能性がある場合は排他処理の考慮が必要
・勝手に編集されたくない場合、隠しファイル(Hiden属性を付与)すると有効だったりする
・暗号化するのが正解だったりするんですが

posted by RR at 22:35 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2017年03月09日

複数の値を返す方法 C#7.0版ではタプル型を使う

Visual Studio 2017正式版がリリースされたので、早速インストールして新登場C#7.0を試してみました。

相変わらず公式な言語仕様はどこにも公開されていないようですが、新機能のTupleで躓きました。

Tuple自体は.NET4.0で追加されたクラスでしたが、今回はC#の言語機能として追加されいます。
以下のように複数の値を纏めて扱えます。

       public (int height,int wieght) GetValues7(int index)
        {
            return (175, 60);
        }
しかしコンパイルが通りません。こんな例外です。

コンパイラの必須型'System.Runtime.CompilerServices.TupleElementNameAttribute'が見つからないため、タプルを利用するクラスまたはメンバーを定義できません。参照が指定されていることを確認してください。定義済みの型'System.ValueTuple2'は定義またはインポートされていません

だそうです。どうやら参照が必要らしい。調べてみるとSystem.ValueTuple.dllへの参照が必要なようです。Nugetします。

SystemValueTuple.PNG
↑よく読み取れませんが、System.ValueTupleで検索してヒットしたのを選択してインスコしました。
作成者がMicrosoftでProvides the System.ValueTuple structs,which implement the underlying types of C# 7.0 tuples.とあります。

これでコンパイルが通って正常に実行できました。
posted by RR at 22:42 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする

2017年03月12日

祝日の求め方 その2

休日はカレンダークラスにあるGetDayOfWeekメソッドを使えば曜日が取れます。

祝日は共通ライブラリからは取れません。いつが祝日かわからないからです。

基本的には「国民の祝日に関する法律」という法律で決まっていますが、常に改正される可能性がありますし、特別法などにより変わってくる可能性があるためです。

そのため、休祝日カレンダーなどが必要となるシステムでは、祝日カレンダー(若しくは営業日カレンダー等)のデータを自前で保持することになります。若しくは内閣府のこちらのページやGoogleカレンダーなどから取得するコードやサービスを用いたりします。

こういうのは内閣府なり国立天文台なりがAPIでも公開すればいいのになんて議論は何度もあったのですが、その声が届いたのか、休日データがCSV形式で公開されました。

最初に公開されたCSVはコンピュータで処理することを前提としない糞みたいな内容であり、こうしたサービスを期待していたエンジニア全員から多くの嘲笑と非難とを集めましたが、その内容を汲み取った形跡が伺えるフォーマットにシレっとなおしてありました。

修正後も問題はあるんですが、とりあえずないよりましですので、これをC#から使う方法の紹介です、

CSVのパスはここです。

http://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv

このパスからデータを読み取るコードは以下になります。

            // add reference to System.Web.dll
            string _path = @"http://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv";
            var client = new System.Net.WebClient();
            byte[] buffer = client.DownloadData(_path);
            string str = Encoding.Default.GetString(buffer);

こんなデータが取得されます。

国民の祝日月日,国民の祝日名称\r\n2016-01-01,元日\r\n2016-01-11,成人の日\r\n2016-02-11,建国記念の日\r\n2016-03-20,春分の日\r\n2016-04-29,昭和の日\r\n2016-05-03,憲法記念日\r\n....

改行コードで分割して一行目のタイトルを飛ばして日付と名称の辞書を作るにはこんな感じ

            var dic = new Dictionary<DateTime, string>();

            string[] rows = str.Split(new string[] { Environment.NewLine },StringSplitOptions.RemoveEmptyEntries);

            rows.Skip(1).ToList().ForEach(row =>
            {
                var cols = row.Split(',');

                dic.Add(DateTime.ParseExact(cols[0], "yyyy-MM-dd", null), cols[1]);
            });

ここまでくれば、どうとでも使えますかね?

やはり無理だよなぁ。いつまでサービスが続くのか、フォーマットが維持されるのか、いつデータ変更されるのか、パスは一定なのか、何の保証もないんだもの。

内閣府も折角なので、ちゃんと作ろうよ。みんな期待しているよ。

posted by RR at 18:12 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする