かたちづくり

つれづれに、だらだらと、おきらくに

.NET 4.0 の場合 FSharp.Core が色々と謎

教えてエライ人~(ToT)

謎現象に悩まされて会社の同僚と一緒にハマっております。何か Visual Studio がややこしいことになってる気がします。

出来るだけ状況を整理して書いてみますが、正直なところ混乱しているのでうまく整理できる自信なし。あと、同僚と一緒に調べているので必ずしも僕が自分で確認した現象だけではないのでその点も悪しからず。

F#のプロジェクト作成 → FSharp.Core 4.3.0.0 を参照している

F# プロジェクトを作ったとします。
参照設定を確認すると、参照している FSharp.Core のバージョンは 4.3.0.0 です。

FSharp.Core 4.3.0.0 を開発で使用するには .NET 4.5 が必要

こうして作成したモジュール(アセンブリ)を共同開発しているお客様にお渡ししました。お客様の開発環境は

というものでした。ここで問題が発生しました。
Visual Studio 2010 の参照設定に、FSharp.Core 4.3.0.0 に依存したモジュールが追加できないのです。「それは .NET アセンブリではありません」的なエラーが出て読み込めませんでした。
結論として、お客様の開発環境に .NET Framework 4.5 をインストールして頂いたところ問題が解消しました。

箇条書きで整理:

  • そのお客様には、VS2010 でビルドした FSharp.Core 4.0.0.0 に依存したモジュールを渡したことがあり、その時は問題なく動作していた。こちらの環境を VS2012 にアップグレードし、FSharp.Core 4.3 に依存するようになったところで問題が発生した。
  • .NET のターゲットを 4.0 でビルドしたにも関わらず、開発環境に .NET 4.5 が必要とされた。
  • 実行環境には .NET 4.5 は要求されない様子。.NET 4.0 のみの仮想マシン環境で実行モジュールは動作した。

どうも不思議な動作なので、「FSharp.Core は 4.3 をやめて 4.0 にダウングレードするほうが無難かも」と思ったのですが・・・次々と謎現象にぶつかりよく分からないことになっております。

FSharp.Core を参照から外してもコンパイル出来る、が・・・

参照設定から FSharp.Core を削除してもコンパイルできてしまいます。F#プロジェクトは自動的にFSharp.Coreが参照されるような仕掛けが何か動いているのでしょうか。まあ、それはそれでいいんですが・・・。

何故か Seq.where を使っている箇所があるとコンパイルエラーになります。代わりに Seq.filter を使えばコンパイルが通ります。

Seq.where は Seq.filter のシノニムで、FSharp.Core 4.3.0.0 から追加された関数のようです。オブジェクトブラウザで確認すると、FSharp.Core 4.0.0.0 の SeqModule には Where が含まれませんが、4.3.0.0 には Where が含まれていることが分かります。

ということは、参照先が FSharp.Core 4.3.0.0 から 4.0.0.0 に変わってしまったということでしょうか。参照設定から FSharp.Core 4.3.0.0 を外すと、デフォルトの FSharp.Core として 4.0.0.0 が選択されるのでしょうか。

実行時に FSharp.Core のバージョンを確認すると 4.3.0.0

ところが、

printfn "%A"
<| System.Reflection.Assembly.GetAssembly( typeof<list<int>> ).FullName

というコードで実行時に FSharp.Core のバージョンを確認すると

"FSharp.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

と表示されます。実行時にロードされているのは 4.3.0.0 のようです。

2013-05-22 追記

この原因は分かりました(たぶん)。
F# プロジェクト作成時に自動生成されていた App.config に次のように書かれていました。

    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="2.0.0.0" newVersion="4.3.0.0" />
        <bindingRedirect oldVersion="2.3.5.0" newVersion="4.3.0.0" />
        <bindingRedirect oldVersion="4.0.0.0" newVersion="4.3.0.0" />
      </dependentAssembly>
    </assemblyBinding>

つまり 4.0.0.0 を参照していても実行時に 4.3.0.0 にリダイレクトされていた、ってことでしょうか。この bindingRedirect をコメントアウトしたところ、4.0.0.0 がロードされるようになりました。
Twitter で @igeta さんよりヒントを頂きました。ありがとうございました。)

参照先を FSharp.Core 4.3.0.0 に戻せない

再び参照設定で FSharp.Core を 4.3.0.0 に戻したところ、何故か Seq.where のエラーが直りません。4.3.0.0 を参照すれば SeqModule に Where が定義されているはずなのに何故・・・。
不審に思って再度参照設定を確認すると、あーら不思議、FSharp.Core のバージョンが 4.0.0.0 に勝手に書き換わっています。何度やってみても結果は同じでした。
どうやら一旦 FSharp.Core の参照を外してしまうと、Visual Studio 上では二度と FSharp.Core 4.3.0.0 を参照させることが出来なくなってしまうようです。ちなみに fsproj ファイルをテキストエディタで開いて書き換えればOKでした。

MissingMethodException ?

上記の挙動は謎ですが、とにかく今の目的は FSharp.Core 4.0.0.0 に切り替えることですから、謎は放置して 4.0.0.0 に切り替える作業を進めました。
ところが、何故か実行時に MissingMethodException が出るようになりました。訳がわかりません。オブジェクトブラウザで確認すればそのメソッドが存在していることは確認できますし、そもそも静的型付け言語を使っているのに MissingMethodException が出るなんて変です。
これは再現手順がきちんと整理できていないので FSharp.Core が原因だという確証を掴んだわけではなく、私の不手際の可能性も残っていますが・・・。

2013-05-22 追記

憶測に過ぎないのですが、もしかして上に追記した App.config の bindingRedirect の件と関係があるかも、などと思い始めました。
この例外は C# のプロジェクトから F# のアセンブリを呼び出した時に発生していました。そして C# のプロジェクトに自動で追加される app.config には FSharp.Core をリダイレクトするような設定は書かれていません。C# 側の app.confing にも bindingRedirect を記述すればもしかして直るのかな・・・などと思いました。(今もろもろ元に戻してしまったので現象がすぐには再現できず、これで直るのかどうか確認できてないのですが)
→ 直りました!!

結局どうしたらいいのん?

今のところお客様に .NET 4.5 をインストールして頂いて作業は進められているので、このまま行くしかないのかなあ。なんか気持ち悪いなあ。
どうしてこうなるのか、メカニズムがハッキリするだけでも心が落ち着くんだけど。

どうも自分はググるのが下手なのか、検索しても本件の答えを見つけられず・・・。
どなたかご存知のかたは教えて下さい m(_ _)m

2013-05-22 さらに追記(結論?)

@yukitos さんのタレコミで似たような問題でハマっている Stack Overflow の記事を知りました。
f# 3.0 - How to generate C#-friendly, .Net 4.0 compatible types using F# 3.0 type providers - Stack Overflow
やはり .NET 4.0 しかインストールされていない環境でビルドにトラブルが生じている様子です。この記事の中で Brian さんという方が

FSharp.Core 4.3.0.0 does not depend on anything in .Net 4.5, it only depends on .Net 4.0.

と書いていますので、.NET 4.0 の環境でも FSharp.Core 4.3.0.0 は問題なく利用(実行)できるようです。Brian 氏はプロフィールに "I’m a developer on the F# team at Microsoft." と書いてありますから、このコメントは信頼出来ます。
ただし開発環境には .NET 4.5 が入っていないとNGのようですので、開発環境にのみ .NET 4.5 を入れる必要があります。整理すると、結論は次のようになるかと思います。

  • FSharp.Core 4.3.0.0 は .NET 4.0 のみの環境でも実行可能。実行環境には .NET 4.5 は必要ない。
  • ただし VS2010 で FSharp.Core 4.3 を利用する場合は、その開発環境にのみ .NET 4.5 をインストールする必要がある(これが最もシンプルな解決策)
  • 以上により、VS2012 では FSharp.Core 4.0.0.0 を使う必要はない。

(@yukitos さん、大変ありがとうございました)