Xamarin.Forms ビルドメモ

以前より懸案だった、OSS になった Xamarin.Forms を弄ろうと思ってコードを DL してざっと目を通しているところ。

xamarin/Xamarin.Forms: Xamarin.Forms official home
https://github.com/xamarin/xamarin.forms

image

たくさんのプロジェクトがあるが、肝なところは、

  • Xamarin.Forms.Platform
  • Xamarin.Forms.Core
  • Xamarin.Forms.Pages
  • Xamarin.Forms.Xaml
  • Xamarin.Forms.Build.Tasks
  • Xamarin.Forms.Xaml.Xamlc
  • Xamarin.Forms.Xaml.Xamlg

なところですかね。

Xamarin.Forms をビルドする

Makefile があるが、Windows 上では動きません。Linux 上でビルドします。

  1. Ubuntu に github からダウンロードして
  2. Install Mono on Linux | Mono を参照にインストール
    1. mono-devel
    2. mono-complete
    3. referenceassemblies-pcl … これがないと、/usr/lib/mono/xbuild-frameworks/ が空かも。
  3. あとは、make すれば ok. *.dll は 各プロジェクトの bin/Release 等の下にできるので、あとでかき集める?
$ ls -la /usr/lib/mono/xbuild-frameworks/
total 16
drwxr-xr-x  4 root root 4096 Sep 13 01:19 .
drwxr-xr-x 16 root root 4096 Aug 29 02:26 ..
drwxr-xr-x 11 root root 4096 Aug 29 02:20 .NETFramework
drwxr-xr-x  5 root root 4096 Sep 13 01:20 .NETPortable

ビルド時に PCL を参照するのですが、先頭が . になっているので ls –a を付けないと確認できないという罠があります。

あとは、出来上がった *.dll を Xamarin.Forms プロジェクトのものと差し替えてやればいいんじゃないかな。たぶん、ローカルの nuget フォルダーを作って、そこから適当なバージョンでダウンロードできるようにするとよいでしょう。

Xamarin.Forms.Xaml.Xamlg が *.g.cs を作成する

いくつか見ていくと、結構面白いものがみつかります。XAML を使うと、コードビハインドの対象として、*.g.cs ファイルが作られます。XAML で書かれていた x:Class とか x:Name とかは、このコードに変換されるんですよね。でもって、これが C# なゆえに、F# の XAML が使えなくてタイププロバイダーで XAML を作ったりするんですが、F# のビルド時の DLL と Xamarin で実行するときの DLL が異なるからうまくいかない、という変な状態に陥っている訳ですが(この変な状態は、以前調査したときよりも分かるようになったので、別の機会に解説を)、ならば、*.g.fs を出力するようにすればよいわけです。そうすると、Xamarin.Forms の XAML に対してコードビハインドの F# コードを作るようにできるので、XAML を扱う F# が書けますよね。まあ、現状の F# の場合は PCL の問題があって構成がややこしくなるので、.NET Core 版の Xamarin が出て来たときにもうちょっと突っ込んだほうがいい気もしますが。

ちなみに、以前つくていた、moonmile/XFormsPreviewer は、動的に XAML をロードして、XAML のタグから直接 x:Name をバインドする方法なので、タイププロバイダーともコードビハインドの *.g.cs 方式とも違います。このあたりは、状況にあわせていろいろな方法があったほういいので。

でもって、もう少し、Xamarin.Forms.Xaml.Xamlg を眺めてみて、F# コードにコンバートできるか試してみますか。というところ。

カテゴリー: Xamarin | Xamarin.Forms ビルドメモ はコメントを受け付けていません

dynamic を利用して ExDoc を書き直してみる(1)

「メタプログラミング .NET」をざっと読み終わったので、手元の github から ExDoc をダウンロードしてきて、dynamic 版の ExDoc を作っているところです。

旧 ExDoc は、演算子のオーバーライトと暗黙のキャストを使って、/,*,%演算子を使って XML を探索できていました。まあ、奇妙といえば奇妙だけど、LINQ の派生みたいな感じで作れるわけです。タグを指定するのが文字列なのがあれなのと、だったら XPath で指定してもいいんじゃないか、という話もありますが、まあ実験的に。ちなみに、このライブラリは実運用で4年ほど使っています。CakePHP をサーバーにして、クライアント側の XML パースを ExDoc を使ってパースしているという技ですね。

public void TestQuery()
{
    var xml = "<root>" + 
        "<person id='1'>masuda</person>" +  
        "<person id='2'>tomoaki</person>" +  
        "<person id='3'>yumi</person>" +  
        "</root>";

    var doc = ExDocument.LoadXml(xml);

    // query element
    ExElement el = doc * "person" % "id" == "2";
    Assert.AreEqual("tomoaki", el.Value);
    // query elements
    ExElements els = doc * "person";
    Assert.AreEqual(3, els.Count);
    Assert.AreEqual("masuda", els[0].Value);
    Assert.AreEqual("tomoaki", els[1].Value);
    Assert.AreEqual("yumi", els[2].Value);
}

で、常々、PHP の SimepleXML がうらやましかった訳ですが、そんな風に直接プロパティで設定できるのが、dynamic の良いところです。まあ、インテリセンスが効かなくなってしまうのですが、どうせ XML タグ名なのだから、そこは割り切りということで。dynamic で受けて、指定の型へキャストし直して使う方法もあるので、それもおいおいと実装する予定。「メタプログラミング .NET」にも書いてあるけど、複数のインターフェースを使って、片方のインターフェースを隠す技が使えます。確か、Xamarin.Forms の XAML ツリーのところで使っています。

dynamic 版の ExDoc を使うと、こんな風に XML タグをプロパティのように扱えます。属性に連想配列を使っているのは、SimpleXML の真似でもあるのと、doc.person.@id のようにはできなかったからなのです。先頭の @ は有効になるんだけど、TryGetMember メンバでだと、「id」としか取れないので、「@id」と「id」は同じ名前として扱われるためです。残念。

public void TestQuery()
{
    var xml = @"
<root>
<person id='1'>masuda</person>
<person id='2'>tomoaki</person>
<person id='3'>yumi</person>
</root>
";
    var doc = ExDocument.LoadXml(xml);
    // query element
    var el = doc.person["id"] == "2";
    Assert.AreEqual("tomoaki", el.Value );
    // query elements
    var els = doc.person;
    Assert.AreEqual(3, els.Count);
    Assert.AreEqual("masuda", els[0].Value);
    Assert.AreEqual("tomoaki", els[1].Value);
    Assert.AreEqual("yumi", els[2].Value);
}

まあ、それでも、連想配列は属性で、配列は子要素で示せるわけで、なかなかいい感じでパースができます。

子孫要素も含めて調べるときは「*」演算子のかわりに、タグ名に「_」を付けます。プロパティ名やメソッド名を後付けでできる(実行時に内部で分解して分岐させる)のが dynamic の良いところです。

public void TestSelectAttr()
{
    var xml = @"
<html>
<body>
<a id='a1' href='link001.html'>title</a>
<a id='a2' href='link002.html'>title</a>
</body>
</html>
";

    var st = new StringReader(xml);
    var doc = XDocument.Load(st);
    var edoc = ExDocument.Load(doc);

    var el = edoc._a["id"] == "a2";
    Assert.AreEqual("title", el.Value);
    Assert.AreEqual("link002.html", el["href"]);
}

これらをどう実装しているかというと、参照だけならば以下で ok です。

– System.Dynamic.DynamicObject を継承する
– TryGetMember を実装する
– TryGetIndex を実装する
– == 演算子を再定義する。

まあ、先行きは値の設定までやるので、TrySetMember や TrySetIndex も実装していきます。
具体的な説明は、.net core の DynamicObject のコードを読むか(コードに詳しい説明が書いてあります)、「メタプログラミング .NET」にざっと書いてあります。

public class ExElement : System.Dynamic.DynamicObject
{
    public string TagName { get { return _el.Name.LocalName; } }
    public string Name {  get { return TagName; } }
    public string Value { get { return _el.Value; } }
    internal XElement _el = null;

    public ExElement( XElement el = null )
    {
        _el = el;
    }
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        var els = new ExElements();

        if (binder.Name[0] == '_')
        {
            string name = binder.Name.Substring(1);
            els = SelectNodes(_el, els, name, true);
        } else
        {
            els = SelectNodes(_el, els, binder.Name);
        }
        result = els;
        return true;
    }
    private ExElements SelectNodes( XElement x, ExElements els, string name, bool deep = false )
    {
        foreach (var el in x.Elements())
        {
            if (name == el.Name)
            {
                els.Add(new ExElement(el));
            }
            if (deep)
                els = SelectNodes(el, els, name, true);
        }
        return els;
    }

    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    {
        result = ExDocument.EmptyElement;
        if (indexes.Count() == 1)
        {
            if (indexes[0] is int)
            {
                int i = (int)indexes[0];
                if (i < _el.Parent.Elements().Count())
                {
                    result = new ExElement(_el.Parent.Elements().ToList()&#91;i&#93;);
                }
            }
            else if (indexes&#91;0&#93; is string)
            {
                string s = indexes&#91;0&#93; as string;
                var attr = _el.Attribute(s);
                if (attr != null)
                {
                    result = new ExAttr(attr);
                }
            }
        }
        return true;
    }

    /// <summary>
    /// 探索
    /// </summary>
    /// <param name="lst"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static ExElements operator ==(ExElement el, string value)
    {
        var result = new ExElements();
        foreach (var it in el._el.Elements())
        {
            if (it.Value == value)
            {
                result.Add(new ExElement(it));
            }
        }
        return result;
    }
    public static ExElements operator !=(ExElement el, string value)
    {
        var result = new ExElements();
        foreach (var it in el._el.Elements())
        {
            if (it.Value != value)
            {
                result.Add(new ExElement( it));
            }
        }
        return result;
    }
    public override bool Equals(object obj)
    {
        return base.Equals(obj);
    }
    public override int GetHashCode()
    {
        return base.GetHashCode();
    }

    /// <summary>
    /// 文字列にキャスト
    /// </summary>
    /// <param name="el"></param>
    public static implicit operator string(ExElement el)
    {
        return el.Value;
    }
    /// <summary>
    /// 数値にキャスト
    /// </summary>
    /// <param name="el"></param>
    public static implicit operator int(ExElement el)
    {
        return int.Parse(el.Value);
    }
    /// <summary>
    /// 実数にキャスト
    /// </summary>
    /// <param name="el"></param>
    public static implicit operator double(ExElement el)
    {
        return double.Parse(el.Value);
    }
}

もうちょっと整理して、前バージョンの ExDoc と同じように使えたら NuGet にアップしましょう。

カテゴリー: C#, EXDoc | dynamic を利用して ExDoc を書き直してみる(1) はコメントを受け付けていません

ぐるっと巡って、再び DLR へ

一時期、ブームだからと思って手に取った(リフレクションやT4目当てで)「メタプログラミング .NET」なんですが、本棚を整理するときにうっかり捨ててしまったらしく、Kindle で買いなおしました → Amazon.co.jp: メタプログラミング.NET 。どうせだから、洋書で買おうかと思ったけど、英語版の Kindle は無くて、翻訳版のほうが安いってのはどうなの?

後付けでメソッドを付けるというのを考えて、最近 Python でそれができるのを知って、そういえば IronPython ってどうなったの?と思って、再び dynamic と System.Dynamic.ExpandoObject を見直し見たら、ああ以前に ExDoc の expando 版を作ろうと思っていた残骸が HDD に残っていたのを再発見、ってのが現在位置です。でもって、「メタプログラミング .NET」の第8章の最後をよんで、やっぱり DLR って死んでたのね、と想った次第。

Iron系のプログラミングは、現在どうなっているのかわかりませんが(書籍で言及しているのが WinRT 直前だったりするので)、現在 dynamic が使われいるところとすれば、

  • ASP.NET MVC の ViewBag
  • Office COM との相互運用(主に Excel やね)

ってところでしょう。Office 組み込みの .NET は完全に死んだ感じがするし、JavaScript プラグインは生きているのか死んでいるのかも分かりません。Excel VBA は未だに健在なわけですが、VBA 自体(あるいは VB6.0)の完全互換な後継を作らない限り、Office 系の VBA はそののままで、Office 386 あたりのブラウザ版が完全に「代替」になる(デスクトップ版のExcelを駆逐するという意味で)になるかは、怪しいですよね。

特に、C# や F# に別の言語体系を組み込みたいわけではないので、DLR を詳細にやろうとは思わないのですが(Unix的には、別コマンドで呼び出せばいいわけだし)、基本的な C# の使い方とはちょっと違う面を、C# の世界に取り込みたいという願望はあります。ExDoc の演算子のオーバーロードと暗黙のキャストを多用した方式や、 D4Reflection でプライベートメソッドをリフレクションを使って呼び出してみたり、XFormsPreviewer で動的に XAML を読み込んで動的にイベントメソッドに結び付けたり、という感じで、少しメタちっくに動かしています(まあ、それぞれ頓挫しているわけですが)。

メタプログラミング .NET 当時は、System.Dynamic.ExpandoObject の詳細は見れなくて(M$ と契約している場合は別だろうけど)あれこれと、互換コードを作るしかなかったのですが、.NET Core としてオープンソース化されたので、dynamic も ExpandoObject のコードも github 上のcorefx/ExpandoObject.cs at master · dotnet/corefx で確認ができます。また、Xamarin.Forms で XAML をローディングしているところも、Xamarin.Forms 自体がオープンソースになったので、Xamarin.Forms/XamlLoader.cs at master · xamarin/Xamarin.Forms でコードを確認できます。いまのところ、.NET Core に WPF や UWP の XAML は含まれていないのですが、おおむね Xamarin.Forms.XAML と似たコードになっていると推測ができます…つーか、ここの XAML をアセンブリ内のクラスに動的にバインディングしている部分を切り離せば、各種の XAML を Parser と Loading/Binding に分けられたのに、なんでこう一括してしまうかなー、という具合なのですが。まあ、このあたりのコードをまるっと持ってきて、整合性をあわせれば、オレオレ ExpandoObject や XAML 動的ローダーも作れるというわけです。

そんな訳で、System.Dynamic.* あたりを使うと Python と同じように後付けのメソッドやプロパティを作れることが分かりました。ああ、作れるな、ってのが分かって、

class ExDoc : IDynamicMetaObjectProvider,
    IDictionary<string, Object>, 
    ICollection<KeyValuePair<string, Object>>,
    IEnumerable<KeyValuePair<string, Object>>, 
    IEnumerable, 
    INotifyPropertyChanged

な感じで用意はしていたんですよね。 でも、あっという間にくじけてしまったらしい(4年前のコードだったよ)。このあたりで ExDoc を実装していくと、XML のパーサーが、

 
var name = doc.Person.Name; 

のような感じで、プロパティのように XML のタグ名を使うことができます。これは「メタプログラミング .NET」の中にも例があります。

 
// 属性値を取る 
int age = doc.Person[0].Name.@age; 
// 要素の値を比較して、タグを検索 
var person = doc.Person.Name == "masuda"; 

こんな風な裏技ができそうな気がします。dynamic は型推論をする F# と相性が悪いので、ちょっと頓挫していたのですが、RPi + Python の組み合わせでやり直そうと思っているので、もう少し突っ込んで行こうかなと思っています。

そうそう、後で、F# と PCL の闇を書かないと。どうやら .NET Core で PCL の闇が晴れそうな気がするのですが、久し振りに PCL プロジェクトを作ろうとして、こんな風になって、闇が続いていることが判明(苦笑)

image

まあ、.NET Core に統一すればいいだよね、おそらく。

カテゴリー: C# | ぐるっと巡って、再び DLR へ はコメントを受け付けていません

メソッドの後付けを python で考える

以前に、

後付け言語 post を考えてみる | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/5866

というのを考えていたのだけど、実は pytohn でそれができることをつい最近知った。

和訳 なぜPythonのメソッド引数に明示的にselfと書くのか | TRIVIAL TECHNOLOGIES 4 @ats のイクメン日記
http://coreblog.org/ats/translation-of-why-explicit-self-has-to-stay/

Raspberry Pi を触っていると Python コードに触れる機会が多いはずなのだが、Windows IoT Core を乗せたり、mono + F# という組み合わせでやっているうちに遠ざかってしまった(苦笑)ので、全然書いたことがなかった。
機械学習がやりたいわけではないけど(画像の特定ぐらいはやらないと駄目なんだが)、

Raspberry Pi Cookbook for Python Programmers [Kindle edition] by Tim Cox
https://www.amazon.co.jp/gp/product/B00JQEJE12/ref=oh_aui_d_detailpage_o01_?ie=UTF8&psc=1

という、ちょうど RPi と Python が学べそうな本があるので、買ってみた。これ、早くに駆っておくべきでしたよ。これだったら Raspbian で電子工作ができそうです。自分が結構躓いたところが丁寧に解説してありました。
RPi のほうは、手元にある Orange Pi も含めてもう少し突っ込んでやるために、Python を使えるようになろうかなと考えているところですが、その前にあの「self」って何なんだろう、に対する答えの記事にちょうど当たりました。初回にあたったのは自分的に結構ラッキーだったかなと思って、書き残しておきます。

後付けでメソッドを拡張する

ちなみに、ごく最近学び始めたばっかりなので、python2の事情は知りません。pytohn3だけを対象にしていきます。あちこちにサンプルコードがあるのですが、それが2なのか3なのかよくわからんけど、まあ、目的は PPi や組み込み Linux でロボット制御あたりに使うことなので、どっちでもいいのです。
たぶん、似たようなことは javascript の prototype でも作れるだろうけど、演算子のオーバーロードとかラムダ式の書き方とかが、自分にしっくりしてきる(というか F# に似ている)ので、python のほうが相性がよさそうです。

# 何もないAクラス
class A:
    pass
# calc メソッドを追加する
def calc(self, x):
    return x*x
A.calc = calc

a = A()
print("ans. " + str(a.calc(10)) )

# 後付けで上書きする
def calc2(self, x):
    return x*x*x
A.calc = calc2

a = A()
print("ans. " + str(a.calc(10)) )

何もメソッドやプロパティを持たない A クラスがあって、後付けでメソッドを拡張します。いわゆる拡張メソッドなのですが、最初の「def calc(self, x):」なところは、普通にグローバルな関数宣言ですよね。グローバルなクラスのメソッドのstaticメソッドとも言えます。F# なら、

let calc self x = 
    x*x

とするところです。このグローバル関数の self 引数は余分なのですが、Python の場合、後付けで A.calc メソッドを付けるときに効いてきます。

def calc(self, x):
    return x*x
A.calc = calc

C++ ならば、A クラスの calc 関数ポインタにグローバル関数を割り当てる、というイメージですよね。C++ の場合、型チェックがきついので、実際は A:calc ポインタになってしまい、グローバル関数の calc を割り当てることはできません。std::function テンプレートを使って、うまいことをやります。
C# や F# の場合も関数ポインタにして用意することもできるのですが、固定であるならば拡張メソッドを使ったほうが楽です。

type A() =
    do ()
let calc self x = 
    x*x
type A with
    member x.Calc n = calc x n 

空の A クラスを作っておいて、グローバルな calc メソッドを、A.calc メソッドから呼び出します。引数の self が無駄になっているように見えますが、実は、member x.Calc の x な部分に対応しています。つまり、F# であっても、ちょうど self/this な部分を参照できるような仕組みになるわけです。

似たようなコードを C# で書くともっと明確になります。

class A {}

class Global {
    static int Calc( A a, int x ) {
        return x*x;
    }
}

class AEx {
    static int Calc( this A a, int x ) {
        return Global.Calc( a, x )
    }
}

C# の場合、グローバルな関数を置けないので、Global クラスを作って static メソッドにしておきます。このとき、無駄な A クラスへの参照を置くわけですが、拡張メソッドを AEx.Calc として作るときに、this として A クラスのオブジェクトを置きます。つまり、明示的に this が使われていることが分かります。

こうなると、Python のメソッドは、実は C# で言うところの「拡張メソッド」にあたるのではないか?という類推ができます。

class B:
    def calc(self, x):
        return x*x

Python のクラスのメンバ関数にいちいち置かれている self は、実はグローバル関数の self 引数なのかもしれません。
そう思うと、グローバル関数と、クラスのインスタンスメソッド、クラスに後付けされるメソッドの3つの関数が同じ型を持っていることに納得がいきます。なるほど、あいまいな型のままにメソッドが呼び出されているわけではなくて、むしろ型チェックのために「self」が置かれている、と考えることができます。いや、実動作はわからないのだけど、論理的にはそう考えられるという訳です。

それらの関数が、常に関数ポインタとして働き、入れ替え可能であるという点で、もういちど、A.calc に別のメソッドを割り当てることも可能になります。

def calc2(self, x):
    return x*x*x
A.calc = calc2

実行してみると、クラスに割り当てたインスタンスメソッドがきちんと動作します。

class A:
    pass

# calc メソッドを追加する
def calc(self, x):
    return x*x
A.calc = calc

a = A()
print("ans. " + str(a.calc(10)) )

# 後付けで上書きする
def calc2(self, x):
    return x*x*x
A.calc = calc2

a2 = A()
print("ans. " + str(a2.calc(10)) )
print("ans. " + str(a.calc(10)) )

この順番で実行してみると、

ans. 100
ans. 1000
ans. 1000

のように、最初の a.calc の値と2回目の a.calc の値が異なります。同じオブジェクト(インスタンス)であっても、違う結果が出るところが面白いですよね。バグの温床になりそうですが(苦笑)。

もちろん、javascript の prototype プログラムのように、オブジェクトを対象にしてメソッドを追加することもできます。

a.calc = lambda x: x*x*x*x
print("a  ans. " + str(a.calc(10)) )
print("a2 ans. " + str(a2.calc(10)) )

こんな風にラムダ式で a オブジェクトのほうにだけ calc メソッドを上書きしてやると、a オブジェクトのほうだけ計算結果が変わります。

a  ans. 10000
a2 ans. 1000

実際は、こんな風に無造作に後付けで拡張してしまうとえらいことになってしまいそうですが、オブジェクト内の変数を弄ってクラスの振る舞いを変えるのではなく、メソッドそのものを切り替えてしまうのは面白い発想です。ダックタイピングを、クラスの利用者側から強制できるし、メソッド名から類する動作をユーザー側から決められるということでもありますね。先の例で言えば、利用するときに A クラスにあらかじめ calc メソッドがあればそれを使うし、無ければ利用者側が追加できるというイメージです。また、提供側の calc が気に入らなkれば、利用者側から書き替えてしまう。その変更は、提供側の動作にも影響を与えるわけですが、calc メソッドとしての動作が理屈にあっていれば(契約プログラミング的に)入れ替えることも自由なわけです。
利用側から内部に影響を与えるという話は、ライブラリが堅牢であることの根拠を崩すことになる(ライブラリは手をつけられない、privateな空間であるということ。または、一定の委譲ルールでしか触れないということ)のですが、実際は、利用者がライブラリを使う以前にライブラリが存在していなければならず、ライブラリの制作者未来を見通すことができない(完全にはできないので、処方箋を示すことしかできない)ので、利用者が使うときには「未知」にならざるを得ない。ならば、一定のルールでもって、ライブラリ自体に介入することを、利用者側から許してもいいのではないか?というのが「後置型」の発想のもとで、先の python のメソッドの後付けが利用できるなと思ったところです。アスペクト指向やDIよりも、もっとライブラリ内部に踏み込んでよい、という発想です。
このあたりは、もうちょっと python を弄って試してみたいところですね。

カテゴリー: 開発, F#, python | メソッドの後付けを python で考える はコメントを受け付けていません

アリスはアノテーションがお嫌い

アリスはリフレクションがお嫌い | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/8092

の続きで、属性/アノテーションの話を追記。

リフレクションはクラスに対して static な情報だということが解ったら、じゃあ「属性」ってのも static な情報というのが分かるので、このあたりは同根なんだなというのが分かる。ケント・ベック氏の「テスト駆動開発入門」に、とあるプログラム言語を学ぼうとしたときに自動テストツール(xUnit)を書いてみると、パターンが網羅されていて学びやすい、ということが書いてある。実際、xUnit のツールにはデザインパターンが多く使われていて、その実現をそれぞれのプログラム言語がどうやって実装しているのかというのを見ることができる。
よくあるテストメソッドとテストクラスの情報収集の方法に

– C++ の場合は、あらかじめ登録が必要
– Java の場合は、先頭にTestがくっついている
– C# の場合は [TestMethod] 属性を付ける

という違いがある。今だともうちょっと違っているけど、初期のころの xUnit のコードを見ると、その当時の文法で駆使されていて、その後にちょっとずつ他の言語の流儀が取り入れられているのが分かります。
C++ の場合は、リフレクションがないので、テストメソッドをいちいち登録するしかない…けど、テンプレートを駆使して明示的には登録しているようにみえないけど、実は登録してるって方法を取ってる。

TEST_CLASS(UnitTest1)
{
public:
	TEST_METHOD(TestMethod1)
	{
		auto m = new Prada();
		m->setStyle(10);
		m->setColor("Pink");
		auto plist = m->properties;
		for each ( auto prop in plist )
		{
			auto n = prop.first;
			auto v = prop.second;
			void*x = v(m);
		}
	}
};

最近の VC++ でテストプロジェクトを作ると、TEST_CLASSとTEST_METHODというマクロが定義されていて、うまく登録できるようになっている。

初期の Java の場合は、属性/アノテーションがなかったので、実行したいテストメソッドと普通のメソッドを区別するために、メソッドの先頭に「Test」とつけておく。でもって、リフレクションを使って先頭が Test になっているメソッドだけを実行するという方式をとる。
これは、結構シンプルでよいのだけど、メソッドの名前自体に意味を持たせてしまうのが、なんだかなーという感じだったので、最近の JUnit では、「@Test」というアノテーションが付けられる。

public class JunitExampleTest {

    @Test
    public void testFoo() {
		JunitExample example = new JunitExample();
        assertEquals(1, example.foo())
    }
}

Java の後発になる C# の場合は、リフレクションだけでは足りないとみて、属性(Attribute)というのものを付けている。実際の使い方はアノテーションと同じだ。

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var m = new Prada { Style = 10, Color = "Pink" };
        var ti = m.GetType().GetTypeInfo();
        var plist = ti.DeclaredProperties;
        foreach (var prop in plist)
        {
            var n = prop.Name;
            var v = prop.GetValue(m);
            Debug.WriteLine($"prop: {n} value: {v}");
        }
    }
}

当初、属性というものが使われていたときに、他の言語にはアノテーション自体の機能がなかったのと、アスペクト指向が流行り始めていて、そのアノテーション(装飾)と区別がつかなかったという経緯もあって、なんかいまいちな言語仕様な感じがしたのだが、MVCパターンのように動的に Action メソッドを呼び出すようなスタイルを考えたとき、ちょっと便利に使える。

[Produces("application/json")]
[Route("api/People")]
public class PeopleApiController : Controller
{
    private readonly ApplicationDbContext _context;

    public PeopleApiController(ApplicationDbContext context)
    {
        _context = context;
    }
    // GET: api/People
    [HttpGet]
    public IEnumerable<Person> GetPerson()
    {
        return _context.Person;
    }
    // GET: api/People
    [HttpGet]
    [Route("search/name/{value}")]
    public IEnumerable<Person> GetPersonByName([FromRoute] string value )
    {
        return _context.Person.Where( t => t.Name.Contains(value));
    }
    // GET: api/People
    [HttpGet]
    [Route("search/age/{value}")]
    public IEnumerable<Person> GetPersonByAge([FromRoute] int value)
    {
        return _context.Person.Where(t => t.Age >= value);
    }

これは、ASP.NET MVCアプリケーションのルーティングのテスト用に作ったサンプルコードだが、たくさんの属性が使われている。要は、クラスに対して static な情報を属性に詰め込めばよいので、Produces属性でJSON形式を返すとか、Route属性でURL呼び出しの形式を設定するとか、HttpGet属性でGETメソッドだけに呼び出しを限るとかの設定ができる。
これらの情報はDI(インジェクション)という形で、設定ファイルなどに追い出すことも可能なのだが、そもそも設定ファイルとこのControllerクラスが1対1になっているのだったら、その「設定」自体をコードで書こうと外部の設定ファイルに書こうと意味は同じはずだ。ならば、属性で、ということになる。当然、コードに埋め込まれてしまう欠点もあって、ちょっとだけルーティングを変えようと思っても、このコードを変えないといけないので、そういう場合は DI のように設定自体を外付けしてしまったほうがよい。
これは、属性とDIのどちらがいいという話ではなくて、利用するときの状況によりけり、というパターンかなと思っている。一般的な使い分けとしては、コーディング時に決定してしまうもの(あとからの設定では変えられないもの、代替できないもの)は属性に、動作環境によって変わるもの(データベースの接続文字列とか)は設定ファイルに出せばよい。

属性を使わないで実装する

じゃあ、本題として、ルイスはアリスが属性/アノテーションが嫌いだから、属性を使わないで自前で実装するんやという話になっているので、ASP.NET MVC っぽい Action メソッドの呼び出しをどうやって実装するのかを考えてみる。初期の JUnit のように諸々の規約をメソッド名に追加してもよいのだが、あれこれ設定するのは大変だ。

class AliceAttribute : Attribute { }
class DefaultAttribute : Attribute
{
    public string Name { get; set; }
}
[Alice]
class Prada
{
    public int Style { get; set; }
    [Default(Name = "White")]
    public string Color { get; set; }
}

こんな風な Alice 専用の Prada クラスを作ってみる。クラスの属性に「Alice」を付けておくと、Alice 専用のクラスという訳だ。ついていない場合は、普通のクラス。
あと、デフォルトの値を設定できるように、Default属性を作ってみる。

public void TestMethod3()
{
    var m = new Prada { Style = 10, Color = "Pink" };
    var ti = m.GetType().GetTypeInfo();

    var attr1 = ti.GetCustomAttribute<AliceAttribute>();
    var attr2 = ti.GetProperty("Color").GetCustomAttribute<DefaultAttribute>();

    Debug.WriteLine($"prop: Color attr: {attr2.Name}");

}

C# での属性呼び出しは、GetCustomAttributeメソッドを使えばよい。GetCustomAttributeで null が返ってきたら属性が設定されていないという訳で、Alice 属性がついているかどうかが分かる。全ての属性を取りたい場合は、GetCustomAttributes メソッドを使うが、どんな属性にも対応するということはないのであまり使わないだろう。クラス特性を調べるときとかに使うかな。
Default 属性で設定した Name プロパティは、そのまま属性オブジェクトの Name プロパティで見れる。この属性オブジェクトが、クラスやメソッドに設定したときにクラス情報と一緒にくっついてくるというイメージでよい。結局のところ、GetTypeInfoメソッドを呼び出してクラス情報を取るので、リフレクションと同じ方式になる。

これを C++ で書き直してみると、

class AliceAttribute {};
class DefaultAttribute {
public: 
	std::string Name;
	DefaultAttribute(std::string &name) { this->Name = name;  }
};

class Prada2 {
private:
	int _style = 0;
	std::string _color = "";
public:
	int getStyle() { return _style; }
	void setStyle(int v) { _style = v; }
	std::string getColor() { return _color; }
	void setColor(std::string v) { _color = v; }
public:
	static AliceAttribute *Alice;
	static DefaultAttribute *DefaultColor;
	Prada2() {
		Prada2::Alice = new AliceAttribute();
		Prada2::DefaultColor = new DefaultAttribute(std::string("White"));
	}
};
TEST_CLASS(UnitTest2)
{
public:
	TEST_METHOD(TestMethod1)
	{
		auto m = new Prada2();
		m->setStyle(10);
		m->setColor("Pink");

		auto alice = m->Alice;
		auto color = m->DefaultColor->Name;
	}
};

適当な属性クラス(AliceAttribute、DefaultAttribute)を用意して、Prada2クラスの static メンバとして設定しておく。汎用的に作る場合は、リフレクションのときと同じように map を使うのだが、クラス自身の属性とメンバの属性があるので2つ作らないといけないのがちょっと面倒…だけでも、まあできないことはない程度にはわかるだろう。

逆にいえば、クラスの情報として、static で設定しているメンバ変数は属性に置き換えられる、ということだ。リフレクションを使うので、呼び出し側でひと手間かかるが、この設定の命名規約があれこれと悩むよりも、Attribute クラスを適当に作ってしまって属性に対する設定のほうで、あれこれと変えるということもできるということだ。
そういう意味で、

– クラスが外部公開する const 変数
– 属性
– DI の設定

を使い分けていくことになる。

カテゴリー: 開発 | アリスはアノテーションがお嫌い はコメントを受け付けていません

アリスはリフレクションがお嫌い

長年使っていた Perl+C# の WordPress 投稿ツールを、.net core を機会に F# に直そうとしている途中なのですが、そのなかでふと、リフレクションを使わずに XML-RPC を実装するのはさぞしんどいことではないか?
ということに思い至りました。いや、もともと使っていた XMLRPC.NET はインターフェース形式なのであれこれとややこしくなっているんですよね。そのあたりで使い勝手が悪くなってしまっているのと、ストレス発散ののためにも F# で書き直しをしています。

実は、MVCパターンのControllerの呼び出しにもリフレクションが使われます。ブラウザから呼び出されたURLアドレスからActionメソッドの呼び出しがそれで、Controllerクラスのメソッドをあらかじめ登録しているのではなくて、実行時にメソッドを探します。ASP.NET MVC だといちいちコンパイルをしないと駄目だけど、CakePHPのようなパターンだと動的言語なのでスクリプトを直接修正することができます。

そんなわけで、XML-RPC だと

class Prada
{
    public int Style { get; set; }
    public string Color { get; set; }
}

なクラスを

<struct>
  <member>
    <name>Style</name>
    <value>
      <int>10</int>
    </value>
  </member>
  <member>
    <name>Color</name>
    <value>
      <string>Pink</string>
    </value>
  </member>
</struct>

な風にXML形式に直します。実際はmethodCall要素があったり、今だったらJSON形式のほうが楽だろうってことなんですが、WordpressにXMLRPCで投稿すると楽なので自作ツールを作って使っています。

リフレクションを使う

じゃあ C# でXML形式に直すことを考えると、PradaクラスのStyleプロパティとColorプロパティをどうにかして持ってこないといけないので、プロパティを列挙するリフレクションを使えばいいんだね、ということになります。

using System.Reflection;

public void TestMethod1()
{
    var m = new Prada { Style = 10, Color = "Pink" };
    var ti = m.GetType().GetTypeInfo();
    var plist = ti.DeclaredProperties;
    foreach (var prop in plist)
    {
        var n = prop.Name;
        var v = prop.GetValue(m);
        Debug.WriteLine($"prop: {n} value: {v}");
    }
}

こんな風に、GetTypeInfoメソッドでクラス情報を取ってきて、DeclaredPropertiesコレクションでプロパティの一覧を取得、その後で名前と値を取ってきます。GetValueメソッドで取ってきたものは object 型なので、実際に利用するときは元の型に戻さないといけません。型自体は、PropertyType プロパティで取って来れるので、これをそれぞれの型と is 演算子で比較するか、乱暴な場合は as でキャストしてしまいます。
XML-RPCのときは最終的に文字列に直してしまうので、ToString で代用させています。

ちなみに、F# で書くとこんな感じになります。

type Prada() =
    member val Style = 0 with get, set
    member val Color = "" with get, set

[<TestMethod>]
let TestMethod1() =
    let m = new Prada( Style=10, Color="Pink" )
    let ti = m.GetType().GetTypeInfo()
    let plist = ti.DeclaredProperties
    for prop in plist do
        let n = prop.Name
        let v = prop.GetValue(m)
        System.Diagnostics.Debug.WriteLine(String.Format("prop: {0} value: {1}", n, v ))

この列挙したプロパティを使って XML 形式に直すわけですが、そのあたりは XElement を使うと楽にできるので、ちょっと省略。

で、アリプラの本題にいきます。
これって、リフレクションを使うと「クラス情報」を取れることになるけど、内部実装的にはどうなっているんだろうか。いや、そもそも、C++ のようなクラス情報を持たない言語の場合は、どうやって実装すればいいんだろうか?

という話になります。クラス情報なんだから、クラスに紐づいているわけで、new で作成したオブジェクトとは別ですよね。なので、いくつもオブジェクトを作ったとしても、クラス情報とかプロパティの型とかは一種類しかないわけです。Javascriptのようにプロトタイプで後からどんどん付け加えていくというパターンもあるので、プロパティやメソッドが固定というわけではないけれど(実装的には、後付け用のコレクションを用意するわけだし、dynamic型みたいな感じになる)、もともとの固定のプロパティやメソッドをどうやって扱おうかということになります。

リフレクションもインターフェースも使わないで実装する

なので、アリスは「リフレクション」も「インターフェース」も嫌いなので、ルイスは何か別の方法で実装しなければいけません。本来ならば、インターフェースを作ってという話になるけど、できるだけリフレクションっぽい形にしたい。あと、クラスの属性のように付加情報も取り入れたい。

class PradaNew
{
    public static string ClassName = "PradaNew";
    public static Dictionary<string, Func<PradaNew, object>> Properties =
        new Dictionary<string, Func<PradaNew, object>>
    {
        {"Style", m => m.Style  },
        {"Color", m => m.Color }
    };
    public int Style { get; set; }
    public string Color { get; set; }
}

そんな訳で、ルイスは「PradaNew」クラスを作って、付加情報を独自に追加しました。なんか、それっぽい形で取れるように、クラス名と、プロパティ名を渡すと値が取れるような仕組みを作っておきます。
両方とも static プロパティなので、クラスのみに紐づいた情報になりますね。

でもって、PradaNew を使ったリフレクションっぽい形の例がこっち。

public void TestMethod2()
{
    // リフレクションを使わないパターン
    var m = new PradaNew { Style = 10, Color = "Pink" };
    var ti = m.GetType().GetTypeInfo();
    var plist = PradaNew.Properties;

    foreach (var prop in plist)
    {
        var n = prop.Key;
        var v = prop.Value(m);
        Debug.WriteLine($"prop: {n} value: {v}");
    }
}

プロパティ名を Key で、値のほうを Value で取ってくるのが多少乱暴ですが、先のリフレクションと同じようなスタイルになります。
ここにクラスの属性として「HttpGet」とか加えたいときも、ClassName プロパティのように static なプロパティを追加すれば ok ですね。static なプロパティなので、オブジェクトが大量に増えてもメモリは増えません。また、値を取ってくるところは適当にキャッシュしてやれば、通常のメソッド呼び出しと遜色ないスピードが出せそうだということがわかります。

これを、ちょっと C++ で直してみます。

#include <map>
#include <functional>

class Prada {
private:
	int _style = 0;
	std::string _color = "";
public:
	int getStyle() { return _style; }
	void setStyle(int v) { _style = v; }
	std::string getColor() { return _color; }
	void setColor(std::string v) { _color = v; }

public:
	const std::string className = "Prada";
	static std::map <std::string, std::function<void*(Prada*)>> properties;

	Prada() {
		auto func = [](Prada* m) { auto v = m->getStyle(); };
		Prada::properties.insert(std::make_pair(std::string("Style"), [](Prada* m) { auto v = m->getStyle(); return &v; }));
		Prada::properties.insert(std::make_pair(std::string("Color"), [](Prada* m) { auto v = m->getColor(); return &v; }));
	}
};
std::map <std::string, std::function<void*(Prada*)>> Prada::properties;;

int main()
{
	auto m = new Prada();
	m->setStyle(10);
	m->setColor("Pink");
	auto plist = m->properties;
	for each (auto prop in plist)
	{
		auto n = prop.first;
		auto v = prop.second;
		void* x = v(m);
		// void* から元の型へ戻すには?
	}
	return 0;
}

元の型に戻すのには、プロパティの型を調べる必要があるので、もう少し手を入れないといけないけど、だいたいはこんな感じ。map にしてラムダ式を返すようにしたのでややこしいことになっていますが、構造体を作ってプロパティの型を調べるところと、値を取得するところを作ったほうが楽かも。

まあ、こんな実装は普通はしないわけで、内部的にはこんなことになっているなーと思いつつ、リフレクションを道具として使うとよいのではと思った次第。

カテゴリー: 開発 | アリスはリフレクションがお嫌い はコメントを受け付けていません

ASP.NET Core MVC の .NET Core アセンブリは何処からロードされるのか?

結論から言うと、

  • C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0 配下
  • C:\Users\<ユーザー名>\.nuget\packages

からロードされています。ASP.NET Core MVC アプリを動作させると、以下の分だけロードされている。

File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\Microsoft.CodeAnalysis.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\Microsoft.Win32.Registry.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\mscorlib.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.AppContext.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Buffers.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Collections.Concurrent.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Collections.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.ComponentModel.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Console.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Diagnostics.Debug.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Diagnostics.DiagnosticSource.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Diagnostics.Tracing.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Dynamic.Runtime.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Globalization.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.IO.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.IO.FileSystem.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.IO.FileSystem.Primitives.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.IO.FileSystem.Watcher.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Linq.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Linq.Expressions.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Net.Primitives.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Numerics.Vectors.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.ObjectModel.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Private.CoreLib.ni.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Private.Uri.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.Emit.ILGeneration.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.Emit.Lightweight.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.Extensions.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.Primitives.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.TypeExtensions.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Runtime.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Runtime.Extensions.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Runtime.Handles.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Runtime.InteropServices.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Runtime.InteropServices.RuntimeInformation.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Text.Encoding.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Text.Encoding.Extensions.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.Overlapped.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.Tasks.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.Thread.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.ThreadPool.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.Timer.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Antiforgery\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Antiforgery.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Authorization\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Authorization.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Cors\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Cors.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Cryptography.Internal\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Cryptography.Internal.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.DataProtection.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.DataProtection.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.DataProtection\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.DataProtection.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Diagnostics\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Diagnostics.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Hosting.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Hosting.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Hosting.Server.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Hosting.Server.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Hosting\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Hosting.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Http.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Http.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Http.Features\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Http.Features.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Http\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Http.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Mvc.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.ApiExplorer\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.ApiExplorer.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Core\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.Core.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Cors\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.Cors.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.DataAnnotations\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.DataAnnotations.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Formatters.Json\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.Formatters.Json.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Razor.Host\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.Razor.Host.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Razor\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.Razor.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.TagHelpers\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.TagHelpers.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.ViewFeatures\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.ViewFeatures.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Razor.Runtime\1.0.0\lib\netstandard1.5\Microsoft.AspNetCore.Razor.Runtime.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Razor\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Razor.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Routing.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Routing.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Routing\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Routing.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Server.IISIntegration\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Server.IISIntegration.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Server.Kestrel\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Server.Kestrel.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.StaticFiles\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.StaticFiles.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.DotNet.InternalAbstractions\1.0.0\lib\netstandard1.3\Microsoft.DotNet.InternalAbstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Caching.Abstractions\1.0.0\lib\netstandard1.0\Microsoft.Extensions.Caching.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Caching.Memory\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Caching.Memory.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Configuration.Abstractions\1.0.0\lib\netstandard1.0\Microsoft.Extensions.Configuration.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Configuration.EnvironmentVariables\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Configuration.EnvironmentVariables.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Configuration.FileExtensions\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Configuration.FileExtensions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Configuration.Json\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Configuration.Json.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Configuration\1.0.0\lib\netstandard1.1\Microsoft.Extensions.Configuration.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.DependencyInjection.Abstractions\1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.DependencyInjection\1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.DependencyModel\1.0.0\lib\netstandard1.6\Microsoft.Extensions.DependencyModel.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.FileProviders.Abstractions\1.0.0\lib\netstandard1.0\Microsoft.Extensions.FileProviders.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.FileProviders.Physical\1.0.0\lib\netstandard1.3\Microsoft.Extensions.FileProviders.Physical.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Localization.Abstractions\1.0.0\lib\netstandard1.0\Microsoft.Extensions.Localization.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Logging.Abstractions\1.0.0\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Logging.Console\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Logging.Console.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Logging.Debug\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Logging.Debug.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Logging\1.0.0\lib\netstandard1.1\Microsoft.Extensions.Logging.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.ObjectPool\1.0.0\lib\netstandard1.3\Microsoft.Extensions.ObjectPool.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Options\1.0.0\lib\netstandard1.0\Microsoft.Extensions.Options.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.PlatformAbstractions\1.0.0\lib\netstandard1.3\Microsoft.Extensions.PlatformAbstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Primitives\1.0.0\lib\netstandard1.0\Microsoft.Extensions.Primitives.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.WebEncoders\1.0.0\lib\netstandard1.0\Microsoft.Extensions.WebEncoders.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Net.Http.Headers\1.0.0\lib\netstandard1.1\Microsoft.Net.Http.Headers.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.VisualStudio.Web.BrowserLink.Loader\14.0.0\lib\netstandard1.5\Microsoft.VisualStudio.Web.BrowserLink.Loader.dll
File	C:\Users\masuda\.nuget\packages\Newtonsoft.Json\9.0.1\lib\netstandard1.0\Newtonsoft.Json.dll
File	C:\Users\masuda\.nuget\packages\System.Collections.NonGeneric\4.0.1\lib\netstandard1.3\System.Collections.NonGeneric.dll
File	C:\Users\masuda\.nuget\packages\System.Collections.Specialized\4.0.1\lib\netstandard1.3\System.Collections.Specialized.dll
File	C:\Users\masuda\.nuget\packages\System.ComponentModel.Primitives\4.1.0\lib\netstandard1.0\System.ComponentModel.Primitives.dll
File	C:\Users\masuda\.nuget\packages\System.ComponentModel.TypeConverter\4.1.0\lib\netstandard1.5\System.ComponentModel.TypeConverter.dll
File	C:\Users\masuda\.nuget\packages\System.Runtime.Serialization.Primitives\4.1.1\lib\netstandard1.3\System.Runtime.Serialization.Primitives.dll
File	C:\Users\masuda\.nuget\packages\System.Text.Encodings.Web\4.0.0\lib\netstandard1.0\System.Text.Encodings.Web.dll

ASP.NET 廻りと MVC 廻りがごっそり NuGet からダウンロードされていることが分かります。たぶん、これプラス、自前の NuGet パッケージが入るのだと思う。

ロードしているアセンブリのチェックは、Process Explorer で。

image

ちなみに、F# 版の ASP.NET MVC アプリを作ると、

File	C:\Users\masuda\.nuget\packages\Microsoft.FSharp.Core.netcore\1.0.0-alpha-160629\lib\netstandard1.6\FSharp.Core.dll

だけが増えます。F# のアセンブリが NuGet で取れるようになっているので、以前のような PCL の混在であれこれと悩むことはなくなりそうな感じです。このあたりは、ぼちぼち調べていきます。

カテゴリー: ASP.NET | ASP.NET Core MVC の .NET Core アセンブリは何処からロードされるのか? はコメントを受け付けていません

HDD性能の再チェック

Hyper-V 上の Visual Studio 2015 の起動が、むちゃくちゃ遅いので、ちょっと SSD/HDD の性能を再チェックしておく。

HDDのアクセススピードは VMWare に影響するのか? | Moonmile Solutions Blog にある3年前の構成をまだ使っている(途中で SSD が逝ったので、交換しているが)ので、HDD が劣化していなか調べるのも兼ねている。SSD のほうは、2年半ぐらいで逝ってしまったので、なんとも言えないのだけど、極端に劣化している感じはしない。途中で、OS も変わっているので、10% 程度は誤差だろう。

image

Cドライブ (TS512)

image

Dドライブ (WDC)

image

Fドライブ (WDC)

image

Hドライブ (PLEXTOR)

image

D:iso (Hitachi)

image

F:dvd (Hitachi)

image

 

Fドライブ内のHyper-Vで

image

SSD(PLEXTOR)のHyper-Vで

– Windows 7

image

– Windows 10

image

Hyper-V 上で Visual Studio を動かす

SSD(PLEXTOR)とHDD(WDC)上に、Hyper-V で Windows 10 + Visual Studio 2015 の環境を作って起動速度を比較してみる。メモリは4GB を割り当てている。

imageimage

左が SSD で、右が HDD。基本性能が違うので、動作にも差がでるであろうというのは予想できるのだが、結論から言えば、

  • Visual Studio の初回起動時は SSD のほうが圧倒的に早い
  • 2回目以降の起動では、4GB のキャッシュに乗っているためか、速度は変わらない

初回起動の比較

2回目の起動比較

どうやら Windows の起動と同じで、初回のみ我慢すれば HDD もそこそこ SSD と変わらない程度で起動してくれるのが分かる。おそらく 4GB のメモリのキャッシュが効いているのだろう。

となると、メモリを 2GB のぎりぎりにした場合、2回目の起動にも差が出るのではないか、という仮定ができる…が、実際やってみると 2GB の場合でも、2回目の起動は対して違いはない。

とはいえ、別件で C++ のコンパイルをしていると、SSD のほうは若干早かったりするので、Hyper-V アクセスであってもストレージへのアクセススピードは早いほうがいい。いくつか ASP.NET MVC 絡みで Hyper-V の仮想環境を複数動かしているので、SSD 480 GB を追加購入。PLEXTOR のほうは、家庭用 PC に移そうと思うので、移行したらベンチマークを取るつもり。家庭用 PC は Core 2 に Windows 10 を入れているというとんでもなく遅い PC なのだが、ブラウザの閲覧ぐらいだったらこれで十分だったりする。それでも、かなりもたつくので、足回りを早くしたときの確認をしてみたい。

カテゴリー: パフォーマンス | HDD性能の再チェック はコメントを受け付けていません

ASP.NET Core MVC のデバッグ実行がとてつもなく遅くなった時

なんだか、分からないが、Visual Studio から IIS Express でデバッグ実行したときに、とてつもなく遅くなって 500 が頻発した。仮想環境のものや、白 vaio の別の環境では大丈夫なので、どうやら業務 PC の Visual Studio 2015 だけ妙な具合になったらしい。

↓なエラーがでる。このエラーが出るまでとてつもなく長い。

image

どうやら、Microsoft シンボルサーバーに問合せをしているところが遅いらしく、このチェックを外すと普通に早くなる。

image

再確認すると、他の PC では入っていなかったので、どっかのタイミングで自動的に入った模様。あるいは、昨日あたりからシンボルサーバーが落ちているとか?

もうひとつ、ASP.NET MVC のプロジェクト作って、別のフォルダーに移すと、IIS Express につながらなくてやっぱり 500 のエラーになる。エラーを見ると、構成ファイルが読み込めないとのことなのだが、プロジェクト内にある web.config は正常のような気がしないでもない。

image

実は、Visual Studio が作る .vs/config/applicationhost.config に IIS Express のための virtualDirectory 書き込まれてあって、フォルダーを移動したときにこの値が以前の場所を指しているので読み込めなくなる。なんだかなー。

というわけで、配布をしたりフォルダーを移動したときは、マメに .vs フォルダーを消す必要がある。このフォルダーは Visual Studio が IIS Express を起動時に再び作るので消し去っても大丈夫。

参考先

asp.net mvc – Visual Studio Debugging/Loading Very Slow – Stack Overflow
http://stackoverflow.com/questions/12567984/visual-studio-debugging-loading-very-slow

c# – ASP.NET 5 MVC: unable to connect to web server ‘IIS Express’ – Stack Overflow
http://stackoverflow.com/questions/35675747/asp-net-5-mvc-unable-to-connect-to-web-server-iis-express

カテゴリー: ASP.NET | 1件のコメント

CheckBox のチェックの□の色を変更する

いわゆる、↓の□の色を変更します。

image

普通にやると、黒で表示されて、どうにも変更できません。が、XAML のスタイルを使って変更します。これ、何をやっても黒にしかならなくて、このような白い四角を表示したいときには、別途自作するとかしていたんですが、原因がわかりました。

XAML ファイルを Blend で開く。

image

「テンプレートの編集」→「コピーして編集」を選択すると、<Page.Resources> にスタイルが追加される。

アニメーションしている storyboard の中で、

<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Stroke" Storyboard.TargetName="NormalRectangle">
   <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource CheckBoxCheckBackgroundStrokeUncheckedPointerOver}"/>
</ObjectAnimationUsingKeyFrames>

の3行をごっそり消します。
どうやら、ここで設定している CheckBoxCheckBackgroundStrokeUncheckedPointerOver などのリソースが「Black」固定になっているんですよね。テーマ色なので仕方がないんですが、ここの Storoke の色は、Setter Property を使って欲しかったところですね。

<Grid Height="32" VerticalAlignment="Top">
    <Rectangle 
        x:Name="NormalRectangle" 
        Fill="{ThemeResource CheckBoxCheckBackgroundFillUnchecked}" 
        Height="20" 
        Stroke="White" 
        StrokeThickness="{ThemeResource CheckBoxBorderThemeThickness}" UseLayoutRounding="False" Width="20"/>
    <FontIcon x:Name="CheckGlyph" Foreground="{ThemeResource CheckBoxCheckGlyphForegroundUnchecked}" FontSize="20" FontFamily="{ThemeResource SymbolThemeFontFamily}" Glyph="&#xE001;" Opacity="0"/>
</Grid>

でもって、最後のほうにある、NormalRectangle を見つけて、Stroke=”White” で色を設定しておきます。ここだけ設定しても、先の storyboard がそのままだと色が上書きされているのが、色が変わらない原因です。

要は、こんな感じなのを作りたかっただけです。

image

ちなみ、デザイン時のデータバインドを、

d:DataContext="{d:DesignData Source=/data/sample.json, Type=vm:MyViewModel}"

な感じで設定しているのですが、色のテーマを Dark にしようとして、Page タグに RequestedTheme=”Dark” を設定すると、↓な感じにバインドエラーがでます。

image

RequestedTheme を消すと、↓な感じでデザイン時のバインドが効くので、バグっぽいです。

image

なんだかなー。あとで調査してバグ報告しますか。

カテゴリー: C#, XAML | CheckBox のチェックの□の色を変更する はコメントを受け付けていません