2013年1月21日月曜日

ASP.NET MVC 4 の ScriptBundle はデバッグ時に .min.js を出力しない

ASP.NET MVC 4 の ScriptBundle はファイル名の末尾で利用するファイルを選択します。

例えば以下の様な3つのファイルを用意した場合、

  • sample.js
  • sample.debug.js
  • sample.min.js

バンドルの設定を以下のようにした場合を見ていきます。

bundles.Add(new ScriptBundle("~/bundles/sample").Include("~/Scripts/sample*"));

デバッグ時

デバッグ時(web.config の compilation要素の debug属性が true のとき)は、sample.debug.js が選択されます。

<script src="/Scripts/sample.debug.js"></script>

デバッグ時でないとき

デバッグ環境でないとき(web.config の compilation要素の debug属性が false のとき)は、sample.min.js が選択されます。

また、sample.min.js の内容は minify されて出力されます。今回は一つのファイルですが、複数のファイルを一つのバンドルに設定した場合は、それらのファイルを全てくっつけて一つのファイルとしてレスポンスしてくれます。

<script src="/bundles/sample?v=Rk-zyJQ66YShCJwocw4z0jCjm_jhceIZ5m55SxsnveY1"></script>

sample.js のみの場合

sample.js のみの場合を見てみます。

デバッグ時

普通に sample.js が選択されます。

<script src="/Scripts/sample.js"></script>

デバッグ時でないとき

もちろん sample.js が選択されますが、スクリプトの内容は minify されて出力されます。

<script src="/bundles/sample?v=FleCpml6dOgPyNvg3fS5cE-_9YPt2osVzl-MIlVAIPU1"></script>

sample.min.js のみの場合

sample.min.js のみだったらどうなるでしょうか。

デバッグ時

この場合注意が必要です。何も読み込まれません。 Scriptタグもレンダリングされません。

デバッグ時でないとき

sample.min.js が選択され、minify されて出力されます。

<script src="/bundles/sample?v=Rk-zyJQ66YShCJwocw4z0jCjm_jhceIZ5m55SxsnveY1"></script>

sample.debug.js のみの場合

あまりないと思いますが、sample.debug.js のみではどうなるでしょうか。

デバッグ時

sample.debug.js が選択されます。

<script src="/Scripts/sample.debug.js"></script>

デバッグ時でないとき

スクリプトタグは出力されますが、中身は空です。

<script src="/bundles/sample?v="></script>

注意点

というわけで、xxxxx.min.js のみだとデバッグ時に利用できなくなってしまうので注意が必要です。
xxxxx.js を入手して一緒に入れておくか、xxxxx.debug.js が用意されていれば合わせて入れておくとよさそうです。

この記事を書く前は、xxxxx.min.js はデバッグ時でないときは minify されずにそのまま出力されると思っていましたが、minify されるようです。そのまま出してくれてもいいような気がするんですが...

2013年1月16日水曜日

[NHibernate]enumを文字列でマッピングする

NHibernate で enum のプロパティ/列をマッピングするときのメモです。

普通にマッピングすると

以下の様な enum のプロパティをマッピングすると、"Gender" は 1, "Female" は 2 という感じで enum の中身の整数(int)でマッピングされます。

モデル

public enum Gender
{
    Male = 1,
    Female = 2
}
public class Student
{
    public virtual int Id { get; set; }

    public virtual string Name { get; set; }

    public virtual Gender Gender { get; set; }
}

テーブル

テーブルのほうはこんな感じですね。

CREATE TABLE [dbo].[Students] (
    [Id]     INT            IDENTITY NOT NULL PRIMARY KEY,
    [Name]   NVARCHAR (100) NOT NULL,
    [Gender] INT            NOT NULL,
    CHECK ([Gender] = 1 OR [Gender] = 2)
);

マッピング

マッピングはこうです。

<class name="Student" table="Students">

    <id name="Id">
        <generator class="identity"></generator>
    </id>

    <property name="Name"></property>
    <property name="Gender"></property>

</class>

データ

Id Name Gender
1 スタン 1
2 カイル 1
3 カートマン 1
4 ケニー 1
5 バターズ 1
6 ウェンディ 2

文字列でマッピングしたいときは

でも整数ではなく文字列でテーブルに持ちたいときもあります。データをこんな風にしたいときですね。

Id Name Gender
1 スタン Male
2 カイル Male
3 カートマン Male
4 ケニー Male
5 バターズ Male
6 ウェンディ Female

そんなときは NHibernate の IUserType インターフェイスを実装してマッピングに利用します。 例えばこんな感じの基底クラスを用意しておいて、

public class EnumStringType<TEnum> : IUserType
{
    public bool Equals(object x, object y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x == null || y == null) return false;
        return x.Equals(y);
    }

    public int GetHashCode(object x)
    {
        if (x == null) return 0;
        return x.GetHashCode();
    }

    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        var value = NHibernateUtil.String.NullSafeGet(rs, names);

        if (value == null)
            return null;

        return (TEnum) Enum.Parse(typeof (TEnum), (string) value);
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        string stringValue = null;

        if (value != null)
            stringValue = ((TEnum) value).ToString();

        NHibernateUtil.String.NullSafeSet(cmd, stringValue, index);
    }

    public object DeepCopy(object value)
    {
        return value;
    }

    public object Replace(object original, object target, object owner)
    {
        return original;
    }

    public object Assemble(object cached, object owner)
    {
        return cached;
    }

    public object Disassemble(object value)
    {
        return value;
    }

    public SqlType[] SqlTypes
    {
        get { return new[] {NHibernateUtil.String.SqlType}; }
    }

    public Type ReturnedType 
    {
        get { return typeof (TEnum); }
    }

    public bool IsMutable 
    {
         get { return false; }
    }
}

これを派生した、目的の enum 用のクラスを作ればOKです。

public class GenderStringType : EnumStringType<Gender>
{
}

テーブル

あとはテーブルの列の型を文字列に変更して...

CREATE TABLE [dbo].[Students] (
    [Id]     INT            IDENTITY NOT NULL PRIMARY KEY,
    [Name]   NVARCHAR (100) NOT NULL,
    [Gender] NVARCHAR (10)  NOT NULL,
    CHECK ([Gender] = ‘Male‘ OR [Gender] = ‘Female‘)
);

マッピング

マッピングに利用する UserType を指定してあげます。

<class name="Student" table="Students">

    <id name="Id">
        <generator class="identity"></generator>
    </id>

    <property name="Name"></property>
    <property name="Gender" type="Sample.NH.UserTypes.GenderStringType, Sample.NH"></property>

</class>

これで enum を文字列で永続化できます。

補足

例では簡潔にするために省きましたが、整数/文字列どちらで保存する場合も、 実際にはマスタテーブル(この例だと Genders とか)を作って外部キー制約を指定しておいたほうがいいですね。

CREATE TABLE [dbo].[Students] (
    [Id]     INT            IDENTITY NOT NULL PRIMARY KEY,
    [Name]   NVARCHAR (100) NOT NULL,
    [Gender] NVARCHAR (10)  NOT NULL,
    FOREIGN KEY (Gender) REFERENCES Genders (Value) 
);
CREATE TABLE [dbo].[Genders] (
    [Id]     INT            NOT NULL PRIMARY KEY,
    [Value]   NVARCHAR (10) NOT NULL,
    UNIQUE (Value)
);

2013年1月11日金曜日

Jenkinsでのビルド時にNuGetパッケージを復元する

NuGet でパッケージ管理をしているプロジェクトを Jenkins でビルドしようとするとうまくいきませんでした。その解決方法をメモします。

うまくいかない理由

NuGet のパッケージは通常バージョン管理下に置かれません。
なのでソースコードをチェックアウトした段階ではパッケージがなく、ビルド時にパッケージを復元しないといけないことになります。

NuGetパッケージの復元を有効化

Visual Studio からパッケージの復元を設定できます。
ソリューションエクスプローラから NuGet パッケージの復元を設定できるので有効にしてあげます。

ソリューションを右クリックして、「NuGetパッケージの復元の有効化」を選択します。

こんな感じの確認ダイアログが出るのでOKします。

少し待つとソリューションフォルダが作成されてこんなファイルが追加されます。
これらを使ってビルド時にパッケージを復元してくれます。

Jenkinsでビルドしてみる

これでバッチリ!ということで Jenkins でビルドすると、今度はこんなエラーになります。

パッケージの復元は既定で無効になっています。確認のため、Visual Studio の [オプション] ダイアログ ボックスを開き、Package Manager ノードをクリックして、[NuGet がビルド中に存在しないパッケージをダウンロードするのを許可する] チェック ボックスをオンにします。また、環境変数 'EnableNuGetPackageRestore' を true に設定して確認することもできます。

メッセージの通りではありますが、ソリューションで復元を有効にするだけでは復元を実行してくれません。
サーバの環境変数「EnableNuGetPackageRestore」を true に設定すると復元してくれるようになります。

これで解決

これでパッケージが復元され、無事ビルドを行うことができます。

おまけ: アンオフィシャルなパッケージソース

NuGet公式サイトにて配布されいるパッケージは以上の通りでOKですが、もし自前の NuGet サーバを利用して、そちらからインストールしているパッケージがある場合、もうひと手間必要です。
※ 私は社内に NuGet サーバを立ててパッケージ管理していたのでちょっとハマりました。

「NuGetパッケージの復元の有効化」したときに追加された「NuGet.targets」を開きます。このファイル中に以下のような部分があります。

    <ItemGroup Condition=" '$(PackageSources)' == '' ">
        <!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
        <!-- The official NuGet package source (https://nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
        <!--
            <PackageSource Include="https://nuget.org/api/v2/" />
            <PackageSource Include="https://my-nuget-source/nuget/" />
        -->
    </ItemGroup>

丁寧なコメントの通りですが、自前の NuGet サーバを利用したい場合は、コメントアウトしてある箇所をコメントインして、パッケージソースを指定してやる必要があります。

    <ItemGroup Condition=" '$(PackageSources)' == '' ">
        <!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
        <!-- The official NuGet package source (https://nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
        <PackageSource Include="https://nuget.org/api/v2/" />
        <PackageSource Include="https://my-nuget-source/nuget/" />
    </ItemGroup>

「https://my-nuget-source/nuget/」の部分を自前のパッケージソースに変えてあげればOKですね。

2012年12月15日土曜日

[C#]式木とLINQ

式木がどんな風に役に立っているか、みんな大好き LINQ で見ていきたいと思います。
普段 LINQ はモリモリ使っていますが、その中身はボンヤリとしか理解していなかったのでおさらいしてみました。

LINQと式木の概要

例として LINQ to SQL を挙げてみていきます。LINQ to SQL で以下のようなクエリ式を書いた場合、

IQueryable<Student> query =
    from
        student in db.Student
    where
        student.Gender == "Male" 
    select
        student
    ;

db.Student を foreach して Genderプロパティ が "Male" の項目を探す... ではなく。
ご存知の通り実際にはこんな感じのSQL文が生成され、ADO.NET によって実行されます。

SELECT 
    [t0].[Id], 
    [t0].[Name], 
    [t0].[Gender]
FROM 
    [dbo].[Students] AS [t0]
WHERE 
    [t0].[Gender] = @p0
-- @p0 = 'Male'

このSQL文を作る過程に式木が利用されています。まず、上記のクエリ式は実際にはシンタックスシュガーで、以下の様なメソッド式に変換されます。

db.Student.Where(student => student.Gender == "Male");

さらにWhereメソッドは拡張メソッドなので、こうなりますね。

Where(db.Student, student => student.Gender == "Male");

この式がこんな感じの式木として扱われます。

同等の Expression を組み立てるとこんな感じです。

var whereMethod = typeof (Queryable)
    .GetMethods()
    .First(m => m.Name == "Where")
    .MakeGenericMethod(new Type[] { typeof(Student) });

var paramStudent = Expression.Parameter(typeof (Student), "student");

var expression =
    Expression.Call(whereMethod,
        Expression.Constant(db.Student),
        Expression.Lambda<Func<Student, bool>>(
            Expression.Equal(
                Expression.Property(paramStudent, "Gender"),
                Expression.Constant("Male")
                ),
            paramStudent
            )
        )
        ;

LINQ to SQL では結果を取得する際、まず式木を評価・解析することで適切なSQL文を作成してくれています。

LINQ to XXXXX

式自体(式木)を評価することで、LINQ to SQL や LINQ to Entities では式を SQL に変換しますが、LINQ to XML, LINQ to DataSet, LINQ to NHibernate ... などでは式を各種データソースに適切なアクセス方法に変換しています。

このような仕組みのおかげで統一的な式で、各種データソースへのアクセス、操作をうまく統合しています。
これが LINQ (統合言語クエリ) と呼ばれるゆえんであり、式木を最も活用している例でもあります。

応用してみる(?)

例えば LINQ to SQL では一括の UPDATE や DELETE ができません。例えば先程の例で 性別(Gender) が 男(Male) の生徒を全て削除しようと思うと以下のようになります。

var query =
    from
        student in db.Student
    where
        student.Gender == "Male"
    select
        student
    ;

db.Student.DeleteAllOnSubmit(query);
db.SubmitChanges();

この操作は以下の様な SQL を発行します。まず一度 Gender = 'Male' な生徒を全て取得してから...

SELECT 
    [t0].[Id], 
    [t0].[Name], 
    [t0].[Gender]
FROM 
    [dbo].[Students] AS [t0]
WHERE 
    [t0].[Gender] = @p0
-- @p0 = 'Male'

それぞれ1つずつ DELETE句 を発行していきます。

DELETE FROM [dbo].[Students] WHERE [Id] = @p0 -- @p0 = 1
DELETE FROM [dbo].[Students] WHERE [Id] = @p0 -- @p0 = 2
DELETE FROM [dbo].[Students] WHERE [Id] = @p0 -- @p0 = 3
...

これはあまりスマートとはいえないやり方ですね。本来以下のような SQL を発行すべきです。

DELETE FROM [dbo].[Students] WHERE [Gender] = @p0 -- @p0 = 'Male'

このように LINQ to SQL は更新や削除の操作がちょっと弱いです。しかしこの問題に対しては、自前の拡張メソッドを作って上記のような適切な SQL を発行するように実装することで対処できます。
以下の記事が大変参考になりますので、是非ご覧になってみて下さい。

もうちょっと具体的には

今回ご紹介した LINQ の仕組みは、IQueryableインターフェイス 及び IQueryProviderインターフェイス の組み合わせで実現されています。
以下の記事が大変わかり易く参考になりますので、紹介させて頂きます。

IQueryable と IQueryProvider の仕組みが把握できると、独自の LINQ to XXXXX を作ることができて夢が広がりますね。

さいごに

LINQ初心者の方や、式木がよくわからないぜ... という方の参考になれば幸いです。

※ 余談ですが http://ufcpp.net/ さんの C# の情報の充実度はハンパじゃないですね... C# について何か調べると必ずといっていいほど検索で挙がってきます。MSDNよりわかりやすい事が多く、いつも参考にさせて頂いています。

2012年12月4日火曜日

[C#]dynamicと拡張メソッド

ひょっとして dynamic って拡張メソッドもイケるのかな?とふと思ったので試してみました。

こんな感じの適当なクラスと拡張メソッドを用意します。

class Hoge
{
    public string Method()
    {
        return "Hoge's instance method.";
    }
}

static class HogeHelper
{
    public static string ExtensionMethod(this Hoge hoge)
    {
        return "Hoge's extension method.";
    }
}

まずは普通にインスタンスメソッドを dynamic から呼び出します。

var hoge = new Hoge();
dynamic dynamicHoge = hoge;

System.Console.WriteLine(dynamicHoge.Method());
 

結果はこんな感じで普通です。

Hoge's instance method.
 

では本題のこうしたときです。

System.Console.WriteLine(dynamicHoge.ExtensionMethod());

拡張メソッドは単なるシンタックスシュガーなので、

hoge.ExtensionMethod();

このような拡張メソッドの利用は、

HogeHelper.ExtensionMethod(hoge);

こんな風に展開されて実行されます。
なので、Hoge に ExtensionMethod というメソッドがあるわけではなく、dynamic はバインドできないはずです。

さっきのコードを実行してみます。

System.Console.WriteLine(dynamicHoge.ExtensionMethod());
Runtime Binder Exception: 'Hoge' に 'ExtensionMethod' の定義がありません

はい、予想通りでした。もしかしたら拡張メソッドもバインドできてしまうのか...!? と思ったのですが、そんなことはありませんでした。

2012年11月28日水曜日

[C#]式木入門

C#の式木について勉強しながら、勉強したことの理解を書いていきます。

式木ってなに?

式木(Expression tree)とは、式(数式)を木構造で表したものの事です。
以下の様な式を例にします。

int result = 5 + 7 * 3; // result == 26
 

この式をみるとき、加算演算子(+)をAdd関数、乗算演算子(*)をMultiply関数とするとこんな感じですね。

int result = Add(5, Multiply(7, 3));
 

ちょっと改行とかインデントを入れてみます。

int result = 
    Add(
        5, 
        Multiply(
            7, 
            3
            )
        );
 

なんとなく木構造にみえてきませんか?図にしてみます。

はい、どーみても木です。このように式は木で表現することができます。これが式木です。

なににつかうの?

式が木で表せることがわかりました。しかしこれは何の役に立つのでしょうか。

式を組み立てることができる

木構造を作ってあげる事で、好きな式を組み立てることができるようになります。

式を分析することができる

式を木構造にしてやることで、式を分析することができます。

つまり...どういうことだってばよ?

プログラム(式)を作ることができます。またプログラム(式)を分析することができます。

C#での式木

C#(.NET Framework)では式木を扱うための仕組みが用意されています。
System.Linq.Expressions 名前空間に式木を扱うためのオブジェクトが揃っています。

C#の中で式木をオブジェクトとして扱えるということは、C#の中でC#を書くというようなことができるということです。
つまり、プログラムの中でプログラムを作ったり、分析したりできます。いわいるメタプログラミングが可能になります。

さっきの式を組み立ててみる

さっきの簡単な式をC#で組み立てる例を示します。

// 式: 5 + 7 * 3
Expression body = 
    Expression.Add(
        Expression.Constant(5), 
        Expression.Multiply(
            Expression.Constant(7), 
            Expression.Constant(3)
            )
        );
 

このように、式は Expression型 として扱います。Expression型にはいろいろな式を表すための派生型が用意されています。
Expression型にファクトリメソッド(上の例のAddメソッドやConstantメソッドなど)が用意されているので、そこから各派生型のExpressionを作成できます。

加算演算子はAddメソッド、乗算演算子はMultiplyメソッドで作れます。5, 7, 3 などの定数はConstantメソッドで作れます。

このようにして作った式を実行したい場合は、以下のようにします。

Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(body) // () => 5 + 7 * 3;
Func<int> func = lambda.Compile();
int result = func(); // result == 26
 

まず、Expression.Lambdaメソッドを使って、作った式をラムダ式に変換します。この場合は「() => 5 + 7 * 3」というラムダ式ですね。

次に作ったラムダ式を実行可能なデリゲートへコンパイルします。
コンパイルした後は通常のデリゲートと同じなので、普通に実行できます。

まとめ

式木を使うことでC#でメタプログラミングができます。プログラムのなかでプログラムを作るという面白いことが可能です。

こんどは

次は実際に式木がどんな事に役に立つのか、みてみたいと思います。

参考にさせていただきました

2012年11月27日火曜日

[C#]Equalsメソッドの実装について

Equalsメソッドと等値演算子(==)の実装、つまり等価比較について自分の理解が怪しかったので改めてまとめてみました。

きっかけは @xin9le さんのこちらのツイートです。

私は謂わいる ValueObject の実装で、よく値比較のクラスを作るために Equals(とGetHashCode) をもりもり実装していたので、「なんか自分間違ってるのかも!?」と心配になりました。

はじめに

まずはじめにEqualsメソッドや等値演算子(==)の実装について、仕組みや注意点、実装ポリシーをおさらいです。以下の記事が大変参考になります。

Equalsメソッドと等値演算子の実装には、いくつかのケースが考えられるので、それぞれ見ていきたいと思います。

値型の等価比較について

値型のEqualsメソッドをオーバーライドしない場合、既定の実装(ValueTypeの実装)により、定義された各フィールドの等価性を調べるように実装されています。

ただし、この比較はリフレクションを用いて行われるので、パフォーマンスが求められる場合は自分でEqualsメソッドをオーバーライドして実装する必要があります。
(参考:値型については equals と等値演算子をオーバーライドします)

また、値型でEqualsメソッドを実装する場合は、等値演算子を実装する必要があります。この場合、Equalsメソッドと等値演算子は同じ結果を返すようにします。

このように値型の等価比較はあまり複雑なことはありません。通常、値型を作成する場合は、Equalsメソッドと等値演算子を合わせて実装するのが良いようです。

話がややこしくなってくるのは、参照型の場合です。

参照型の等価比較について

参照型のEqualsメソッドは、既定の実装では参照の等価性を評価します。
また、等値演算子も同様に参照の等価性を評価します。(参照型なので当然ですね)

しかし、参照型でも値の比較によって等価性を評価する場合があります。
例えば、文字列型(string)は参照型ですが、Equalsメソッドも等値演算子も値の比較で等価性を評価するように実装されています。

これは参照型であっても、文字列型(string)は値のような特性を持つためにこのようになっています。(たぶん)
文字列型(string)が参照比較だった場合、直感的な挙動でなくなると思うので、なんとなくやりづらそうです。

値型と参照型の選択

「じゃあややこしいから値っぽい型は全部値型でいいじゃん!」と思うわけですが、そういうわけにもいかない事情があります。

値型は代入時にコピーされるので、サイズが大きいと高コストになってしまいます。(逆にサイズが十分小さいなら値型のほうが高速です)
また、ボックス化やボックス化解除が頻繁に発生する場合も高コストになります。

そうなると、値のような特性を持つ型でも、サイズが大きい、ボックス化がよく起きる、というようなケースでは参照型のほうが適切になります。(stringはサイズが大きいですね)

まとめ

というわけで、値のような特性を持つ型については、参照型でもEqualsや等値演算子を実装するケースはあります。
ただ「じゃあそれってどのくらいあるの?」となると、私の想像できる範囲だと最初に挙げた ValueObject くらいしか思いつきません。

なので、そういったオブジェクトを作る必要がなければ、実装することはあまりなさそうです。
あと、Equalsの実装にはGetHashCodeを合わせて実装する必要がありますし、その際にも気をつける必要のある実装ポリシーがいろいろあって、なかなか大変です。

なんとなく自分の理解がそれほど外れた所になさそうな気がして一安心ですが、あまり深く考えずにバシバシ参照型で ValueObject を作っていたので、値型で作ったほうが適切でないか見直しが必要そうです。

あと、Equalsを実装する場合は、タイプセーフに比較できる IEquatable<T> インターフェイスを積極的に使ったほうがよさそうです。

Factorio: Space Exploration クリア記録

 工場建設クラフトゲーム、Factorio の MOD である Space Exploration のクリア記録です。 はじめに プレイ時間は約 350 時間、2023年10月から2025年2月にかけて15ヶ月間に及びました。この期間中3人の友人と毎週末、工場勤務に明け暮れました...