くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

2016年01月

.Netのフレームワークが違くて動かない場合の対処

なんか作ったはいいが、別の端末で動かそうとすると、バージョンが入ってねーって怒られる場合の対処。もちろん.Net4.0以降でしか入ってない機能を.Net2.0で動かそうとするのは無理。

コンフィグにアプリがサポートするバージョンを明示しておかないとダメらしい。↓の感じ。

<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
    <supportedRuntime version="v2.0.50727" sku=".NETFramework,Version=v2.0.50727"/>
  </startup>
</configuration>

VisualStudioで言うと、「App1.config」って感じのファイルがあればそれ。なければ追加で「アプリケーション構成ファイル」を追加する。実際には「実行ファイル名.exe.config」みたいなファイルがそれ。

MSDN的には

このアプリケーションがサポートする共通言語ランタイム 
(CLR: Common Language Runtime) のバージョンを指定する文字列値。
CLR の最初の 3 つのバージョンは、"v1.0.3705"、"v1.1.4322"、
および "v2.0.50727" で指定します。
.NET Framework 4 以降では、必要となるのはメジャー バージョン番号と
マイナー バージョン番号のみです (つまり、"v4.0.30319" ではなく "v4.0")。
短い文字列を使用することをお勧めします。
.NET Framework バージョン 3.0 および 3.5 では CLR のバージョン 2.0.50727 が使用されます。

だそうですよ。

C#のコンソールアプリでカーソルを先頭に戻す

コンソールアプリとしてツールを作ってると、処理の進捗とかをコンソールに出してあげたくなる。 普通にやると、メッセージだけでコンソールがどんどんスクロールしちゃうので感じ悪い。 同じ行でカウントアップの結果を表示したりしたいわけです。

で、これをやるためにはカーソルを行先頭に戻して書き直せればいいわけで、 ググるとだいたい↓の感じで出てくる。

Console.SetCursorPosition(0, Console.CursorTop);

これでカーソルが先頭に戻るでそのまま出力しなおせばいい。とゆーわけですが、 世の中そんな甘いもんじゃない。

ありがちな、|とか/をクルクル回すやつとか、「123/234」みたいな進捗出す奴は上のだけでいける。 ↓の感じ。

//カーソルのちらつきがイラつく場合はカーソル非表示に。。。
Console.CursorVisible = false;
for(int i=0;i<10000;i++)
{
        Console.Write("{0} / {1}", i, 10000);//進捗をカウンタで表現する場合
	Console.Write("|/―\".Substring(i%4,1));//クルクル回す場合
	Thread.Sleep(100);
	Console.SetCursorPosition(0, Console.CursorTop);
}

↑みたいに、文字数が固定か、増えていくだけの場合は問題ない。でも、例えば処理してるファイル名を表示している場合で「textdata01.txt」の後に「data01.txt」が来ると↓の感じになる。 test

そうじゃないんだ。

カーソル先頭に戻してるだけなので当たり前といえば当たり前か。これが嫌なので一行クリアしてくれる物がないか探してみたけど・・・無い。Clearはあるけど画面が全部クリアされる。コマンドでいう「cls」。しょうがないの今は↓の感じで対応中。

int prePos = Console.CursorLeft;//現在カーソル位置を取得
Console.SetCursorPosition(0, Console.CursorTop);
Console.Write(hogestr.PadRight(prePos));//前のカーソル位置まで空白埋めする

かっこ悪いけど、無い頭ではこれが限界だったとさ。

C#でMeCabを使う

NuGetにMeCabがあった。テンションあがったので実装を試してみる。

NuGetでインストールしたら、using。

using NMeCab;

そしたら↓の感じ。

public void ana()
{
    MeCabParam mPara = new MeCabParam();
    //辞書ファイルがあるフォルダを指定(NuGetで入れれば勝手に入る)
    mPara.DicDir = @"*****\dic\ipadic";

    MeCabTagger mTagger = MeCabTagger.Create(mPara);

    string sentence = "すもももももももものうち";//解析する文字列
    MeCabNode node = mTagger.ParseToNode(sentence);
    while (node != null)
    {
        if (node.CharType > 0)
        {
            Console.WriteLine("{0}\t{1}", node.Surface, node.Feature);
        }
        node = node.Next;
    }
}

実行結果

すもも	名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
うち	名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ

すばらしい。前はRubyとか使ってゴニョゴニョしてたけど、いろいろできそう。

性能的には、CPU:2.9GHz(4コア),RAM:8GのWindows7で、 とあるニュース記事一本の文章を10000回解析して、51.34秒。 一回あたり、0.0051秒ってところでしょうか。

これだけだとアレなので、少し手を加える。MeCabはすばらしいんだけど、 戻りのインターフェースがCSV形式の文字列なので、いろいろ扱いにくい。 なので、結果を扱いやすくするためのクラスを下の感じで作る。 ↓のクラスの位置のプロパティは文章中の何番目に出てきたかを保持しておくために 追加してます。N-Gram解析とかで使いやすくするため。

public class MecabResult
{
    //結果のリスト
    public List<MecabResultItem> nodes { get; set; }
    //コンストラクタ
    public MecabResult(MeCabNode pnode)
    {
        nodes = new List<MecabResultItem>();
        int itempos = 0;
        while (pnode != null)
        {
            MecabResultItem addItem = new MecabResultItem();
            if (pnode.CharType > 0)
            {
                addItem.表層形 = pnode.Surface;
                string[] tmpStrs = pnode.Feature.Split(',');
                if (tmpStrs.Length == 9)
                {
                    addItem.品詞 = tmpStrs[0];
                    addItem.品詞細分類1 = tmpStrs[1];
                    addItem.品詞細分類2 = tmpStrs[2];
                    addItem.品詞細分類3 = tmpStrs[3];
                    addItem.活用形 = tmpStrs[4];
                    addItem.活用型 = tmpStrs[5];
                    addItem.原形 = tmpStrs[6];
                    addItem.読み = tmpStrs[7];
                    addItem.発音 = tmpStrs[8];
                }
                addItem.位置 = itempos;
                nodes.Add(addItem);
            }
            itempos++;
            pnode = pnode.Next;
        }
    }
    public partial class MecabResultItem
    {
        public string 表層形 { get; set; }
        public string 品詞 { get; set; }
        public string 品詞細分類1 { get; set; }
        public string 品詞細分類2 { get; set; }
        public string 品詞細分類3 { get; set; }
        public string 活用形 { get; set; }
        public string 活用型 { get; set; }
        public string 原形 { get; set; }
        public string 読み { get; set; }
        public string 発音 { get; set; }
        public int 位置 { get; set; }
    }
}

こうしておくとどーなるかと言うと、ラムダ式で検索とか出来ちゃうわけです。↓の感じ。

public void ana()
{
    MeCabParam mPara = new MeCabParam();
    //辞書ファイルがあるフォルダを指定(NuGetで入れれば勝手に入る)
    mPara.DicDir = @"****\dic\ipadic";

    MeCabTagger mTagger = MeCabTagger.Create(mPara);

    string sentence = "すもももももももものうち";//解析する文字列
    MeCabNode node = mTagger.ParseToNode(sentence);
    
    MecabResult mr = new MecabResult(node);//ココで初期化
    //名詞に絞って表示する
    foreach(var tmp in mr.nodes.Where(n=>n.品詞 == "名詞"))
    {
        Console.WriteLine(tmp.表層形);
    }
}

VisualStudioで自前サーバのGit(gitbucket)を使う

ソース管理がしたくなったので、VisualStudioから自前サーバのGitを使えるようにした。外部のサービスは使いたくなかったので、Gitはテケトーなサーバで自前で動かす。今回使ったのはgitbucket。サーバOSはWindows2008。

javaで動かすらしいので、JREをインストール。オラクルからダウンロードしてひたすらクリック。途中、変な機能を入れられようとするのでいつも通りチェックはずす。

入れた直後は多分、パスが通ってないので、サーバ再起動する。コマンドプロンプトで「java」って入れて反応なければパス通ってない。

そしたら、gitbucketをダウンロードして適当なとこに置いておく。ダウンロードするのは「gitbucket.war」ってファイル。

とりあえず、動かしてみる。コマンドプロンプトで「gitbucket.war」があるフォルダまで行って、↓のコマンド

java -jar gitbucket.war

あとは「http://localhost:8080/」をブラウザで開いて、起動確認。id/passは両方root。

デフォで8080で動くけど、いろいろ同居させてるので都合悪し。↓でポート変えて再度起動。

java -jar gitbucket.war --port=8008

そしたら、ログインして、空のリポジトリを作る。 01
02

作った後の画面で出てくる↓のURLをコピッとく。 03

そしたらVisualStudio開く。管理したくなったの開いて、ソリューションを右クリ⇒ソース管理に追加 04

TFSとGitどっち使うって聞かれるからGitっていう。そしたら、チームエクスプローラ開いて、とりあえず変更押す。あとはコメントいれてコミット。コメント入れないとコミットさせてくれないいけず。 05

コミットしたら、同期する?ってでるから同期するって言う。 06

リモートリポジトリのURL入れてって言うからさっきコピッておいたURLを入れる。 07

公開押すと、ID/Pass聞いてくるので、root/rootって入れておしまい。あとはcloneでもpushでもご随意に。

ちなみに、URL間違えるようなやんちゃをするとVisualStudioさんは許してくれない。一回しくじると、URLの変更するとこが出てこないのよね・・・。小一時間ほど探したけど見つけられませんでした。誰か教えてください。しょうがないのでそういう場合は設定ファイル直でいじって無かったことにしてもらう。

対象のソリューションのフォルダの中に「.git」ってゆーフォルダができてるので、その中の、「config」ファイルを開いて、↓のremoteってなってるところまるっと消す。 08
これで、URL入力するところが出てくる。

C#で異なるオブジェクト間でプロパティの値をコピーするVer2

前書いたやつはプロパティしかコピーしてなかったのでフィールドもコピーするバージョン。

//使い方
//CopySameNameProperty(コピー元Object,コピー先Object)
public static void CopySameNameProperty(object from, object to)
{
    //それぞれのタイプを取得
    Type fromType = from.GetType();
    Type toType = to.GetType();
    //メンバーリスト
    MemberInfo[] fromMembers = fromType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
    MemberInfo[] toMembers = toType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
    //名称用のリストを用意
    List<string> nameList = new List<string>();
    foreach (MemberInfo m in toMembers)
    {
        //プロパティとフィールドの場合のみ
        if (m.MemberType == MemberTypes.Property || m.MemberType == MemberTypes.Field)
        {
            nameList.Add(m.Name);
        }
    }

    //同名プロパティとフィールドの値をコピー
    foreach (MemberInfo m in fromMembers)
    {
        if (m.MemberType == MemberTypes.Property)
        {
            //同名のプパティがあれば転送する
            if (nameList.Contains(m.Name))
            {
                toType.GetProperty(m.Name).SetValue(to, fromType.GetProperty(m.Name).GetValue(from, null));
            }
        }
        else if(m.MemberType == MemberTypes.Field)
        {
            //同名のフィールドがあれば転送する
            if (nameList.Contains(m.Name))
            {
                toType.GetField(m.Name).SetValue(to, fromType.GetField(m.Name).GetValue(from));
            }
        }
    }
}

いちを、↓の感じくらいのオブジェクトだったら全部コピーしてくれる。

public class HogeClass
{
    //プロパティ
    public string s { get; set; }
    public string t { get; set; }
    public string r { get; set; }
    public List<int> list1 { get; set; }
    public FugaClass propertyclass { get; set; }
    //フィールド
    public string fieldstr = "";
    public List<int> list2 = new List<int>();
    public FugaClass fieldclass = new FugaClass();
}

javascriptでホイール使って入力する

時間とか日付とかホイールで入力できると割とストレスフリー。なので、jqueryとmousewheel.jsを使って実装してみる。

HTML側

<input type="text" id="hoge" name="hoge" value="0" />

javascript側

時間を入力。

$('input[name=hoge]').mousewheel(function(event, delta, deltaX, deltaY){
    $targetElm = $(this)
    var currentVal = parseInt(targetElm.val());//現在値
    var nextVal = 0;
    nextVal = currentVal + deltaY;
    if(nextVal < 0) nextVal = 23;
    nextVal = nextVal % 24;
    elm.val(nextVal);
    
    //ホイールイベントをここで止める(スクロールを止める)
    if (event.preventDefault) {
        event.preventDefault();
    }
    event.returnValue = false;//これないと効かないブラウザもあった気がする
});

日付を入力。日付の加減算はmoment.jsを使用。エラーチェックなし。

$('input[name=hoge]').mousewheel(function(event, delta, deltaX, deltaY){
    $targetElm = $(this)
    var m = moment($targetElm.val(), "YYYY-MM-DD");
    if(deltaY > 0)
        m.add(1,"days")
    else
        m.subtract(1,"days")
    $targetElm.val(m.format("YYYY-MM-DD"));
    
    //ホイールイベントをここで止める(スクロールを止める)
    if (event.preventDefault) {
        event.preventDefault();
    }
    event.returnValue = false;//これないと効かないブラウザもあった気がする
});


javascriptで画面の全体スクロールさせないようにする

Webで入力する時、例えばテキストエリアとかみたいなので、ホイールクリクリしてエリア内のスクロールだけ動かしたいんだけど、一番上とか下まで行くと、そのまま全体スクロールされる。

個人的にこの動きがホント嫌い。いらっとする。なので、特定のDOM内でスクロールしてる時は全体スクロールさせないためのスクリプト。jqueryとmousewheel.jsを使用。DOM内のスクロールは自分でやらないとダメ。

$('textarea[name=test]').mousewheel(function(event, delta, deltaX, deltaY){
    scrLen = 40
    $targetElm = $(this)
    if (deltaY < 0){
        $targetElm.scrollTop($targetElm.scrollTop()+scrLen);
    } else {
        $targetElm.scrollTop($targetElm.scrollTop()-scrLen);
    }
    //ホイールイベントをここで止める(スクロールを止める)
    if (event.preventDefault) {
        event.preventDefault();
    }
    event.returnValue = false;//これないと効かないブラウザもあった気がする
});

mousewheel.jsが便利。とても便利。

ちなみに、mousewheel.jsが無い版はこんな感じ。

var mousewheelevent = 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll';
$("#hoge").on(mousewheelevent,function(e){
    scrLen = 30;
    var delta = e.originalEvent.deltaY ? -(e.originalEvent.deltaY) : e.originalEvent.wheelDelta ? e.originalEvent.wheelDelta : -(e.originalEvent.detail);
    var $targetElm = $(this);
    if (delta < 0){
        $targetElm.scrollTop($targetElm.scrollTop()+scrLen);
    } else {
        $targetElm.scrollTop($targetElm.scrollTop()-scrLen);
    }
    //ホイールイベントをここで止める(スクロールを止める)
    if (e.preventDefault) {
        e.preventDefault();
    }
    e.returnValue = false;//これないと効かないブラウザもあった気がする
});

C#で異なるオブジェクト間でプロパティの値をコピーする

どこかで、コードの大部分は値の代入で占められているという話を見た。 たしかに、単純転送みたいなものはかなり多い。.Net MVCで作ってると、とゆーかEntityFramework使ってると、ポコポコおぶじぇ祭りになりがち。View用に専用のモデルとか作ってると、テーブルのオブジェクトの値転送とか発狂しそうになる。

なので、2つのObjectで、同じ名前のプロパティの値をコピーするやつを作ってみた。割と便利。

//使い方
//CopySameNameProperty(コピー元Object,コピー先Object)
using System.Reflection;
public static void CopySameNameProperty(object from, object to)
{
	//それぞれのタイプを取得
	Type fromType = from.GetType();
	Type toType = to.GetType();
	//メンバーリスト
	MemberInfo[] fromMembers = fromType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
	MemberInfo[] toMembers = toType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
	//名称用のリストを用意
	List<string> nameList = new List<string>();
	foreach (MemberInfo m in toMembers)
	{
	    //プロパティの場合のみ
	    if (m.MemberType == MemberTypes.Property)
	    {
	        nameList.Add(m.Name);
	    }
	}

	//同名プロパティの値をコピー
	foreach (MemberInfo m in fromMembers)
	{
	    if (m.MemberType == MemberTypes.Property)
	    {
	        //同名のプパティがあれば転送する
	        if (nameList.Contains(m.Name))
	        {
	            toType.GetProperty(m.Name).SetValue(to, fromType.GetProperty(m.Name).GetValue(from, null));
	        }
	    }
	}
}

ただし、ってか当たり前だけど、データ型は合ってないと落ちる。あと、intとかstringあたり以外のオブジェクトだと参照渡しになって、コピー元の値を変えるとコピー先も変わるので一工夫必要かと。。。

ついでに、プロパティを初期化するやつも作ってみた。MVCで作ってると、 新規にモデル作るときに項目がNULLってくるので、初期化があると楽。他にもやり方はあるだろうけども。。。

using System.Reflection;
public static void NullValueInit(object targetObj)
{
	//Typeを取得
	Type targetType = targetObj.GetType();
	//メンバを取得する
	MemberInfo[] targetMembers = targetType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
	foreach (MemberInfo m in targetMembers)
	{
	    //プロパティの場合のみ
	    if (m.MemberType == MemberTypes.Property)
	    {
	        if (targetType.GetProperty(m.Name).GetValue(targetObj, null) == null)
	        {
	            //Int32の場合は0で初期化
	            if (targetType.GetProperty(m.Name).PropertyType.Name == "Int32")
	                targetType.GetProperty(m.Name).SetValue(targetObj, 0);
	            //Stringの場合は""で初期化
	            else if (targetType.GetProperty(m.Name).PropertyType.Name == "String")
	                targetType.GetProperty(m.Name).SetValue(targetObj, "");
	        }
	    }
	}
}

ちょっと改造版

.net MVCのWebAPIでどうしてもPostのデータが取れない時

APIを作ったはいいがPOSTの各データがなんか取れない場合に、とりあえず受け取る方法。 いろいろ試してこれしか無かった。絶対、他に原因があるはず。。。

System.Web.HttpContext.Current.Request.Form["name"].ToString()

.net MVCのWebAPIでServer.MapPath

.netMVCでApiController継承してるコントローラで Server.MapPathが使いたくなった時。

System.Web.Hosting.HostingEnvironment.MapPath("/hoge/moe");
問合せ