MessagePack for CLIと
そこから学ぶ.NETの
マルチプラットフォーム対応
.NET Fringe Japan 2016
藤原 雄介
自己紹介
• 藤原 雄介 @yfakariya
• MVP for Visual Studio and Development Technologies 2015-
• .NET 系の本をいくつか翻訳したり、OSS 書いたりしてます
• 『.NETのクラスライブラリ設計』『プログラミング.NET
Framework』等
• BotR、Azureのドキュメントetc.
• Msgpack for CLIコミッタ、ちょこちょことPR
今日のお題
• クロスプラットフォームな.NETのOSSシリアライザーを週末に
作った話
• 作ったものの説明
• クロスプラットフォーム実装の事例
• ライブラリ、ビルド、テスト
余談:週末OSSるには
• 好きな分野でやる
• 結構好きな分野って重ならないもの
• あえてメジャーどころに手を出さない
• 他に人に追い抜かれてやる気が、とかない
• シリアライザーとか DispatchProxy とか他にやる人いない……はず
• 完璧を求めず、面の皮を厚くする
• バグがあった? OK直すよ(でも今日は寝る)
• コミット履歴が汚い? 知らんがな
• まずはやってみる
• 周囲の理解を得る
MessagePack for CLIとは
シリアライザーライブラリ
• MessagePack for CLIというシリアライザーライブラリ
• シリアライザーとは何か、という哲学的な話はしません
• 参照でつながった一連のオブジェクト(オブジェクトのグラフ構造)
を一直線(シリアル)なバイト列に変換する(そして戻す)コンポー
ネント
MessagePack for CLI
• 相互運用可能なバイナリ形式エンコーディングである
MessagePack(msgpack)の.NET実装
• エンコーダ + シリアライザー
• Msgpack
• バイナリ形式のJSONのようなもの
• バイナリ形式:ProtocolBufferやAvroなど
• Fluentdで使っているフォーマット
• CLI: Command Language Infrastructure,
NOT CommandLine Interface
msgpackエンコーディングのメリット
• JSON に比べて
• 数値のサイズが劇的に小さい
• 特に、-31~127の範囲は 1 バイト
• 文字列のサイズは同じ
• どちらもUTF-8
• 配列のサイズが劇的に小さい
• ヘッダー(1~5バイト) + 要素
• ブレがない
• =と:間違えたり、”つけ忘れたりといった亜種がない
• デシリアライザーの実装が楽
XPLAT
<dependencies>
<group targetFramework=".NETFramework4.6" />
<group targetFramework=".NETFramework4.5" />
<group targetFramework=".NETFramework3.5" />
<group targetFramework="WindowsPhone8.0" />
<group targetFramework="MonoAndroid1.0" />
<group targetFramework="MonoTouch1.0" />
<group targetFramework="Xamarin.iOS1.0" />
<group targetFramework=".NETStandard1.1" />
<group targetFramework=".NETStandard1.3" />
+Unity
• Full
• Mscorlib only
MsgPack.nuspec
動作例(地味注意)
シリアライザーの設計
(たぶん)一般的な
シリアライザーの構成要素
Type Introspector
Encoder/DecoderCache
Validator
API Type Model
Mapper
Type Introspector / Type Model
• シリアライズする対象の型をスキャン
• シリアライズ対象のプロパティやフィールドを列挙
• 特別扱いする型の判定
• コレクション
• String
• プリミティブ
• 内部的に処理しやすいオブジェクトモデルに変換しておく
Mapper
• Type Modelをシリアライズ先のエンコードの表現に変換
• JSONなら、Number、String、Bool、Array、Object、Nullと対応づけ
る
• MsgPackの場合、さまざまな整数型、Real32、Real64、Array、Map、
Ext、Nil、Bool
Encoder / Decoder
• 実際のエンコードを行う
• テキスト系(JSONやXML)なら文字エンコーディングも行う
• バイナリ形式の場合、マッピングした型をそのまま書き出す
• ただし、MsgPackの場合は整数値の圧縮表現を処理する
• -31~127の整数→そのまま書き出す
• それ以外の整数:「型ヘッダー+数値のビット表現」で書きだす
Validator
• デコードするバイト列は、そもそも不正な形式かもしれない
• デコードしたデータは、デシリアライズ(逆シリアル化)でき
ない形式かもしれない
• これらの検証処理が必要
Cache
• シリアライザーの内部オブジェクト群の構築はそれなりにコス
トがかかる
• 型(とオプション構成)に対して一意であるため、一度作れば
使いまわすことができる
• 複数の型から使用される型もあるので、結構有効
• ユーザーが自前のシリアル化ロジックを実装したい場合、その
登録先となりうる。
API
• これまでの要素を内部に隠蔽
• ユーザーにとって使いやすいインターフェイスを提供
• 後方互換性を確保
• 特定の標準インターフェイスへの準拠
シリアライザーの構成要素
Type Introspector
Encoder/DecoderCache
Validator
API Type Model
Mapper
SerializationContext
MessagePackSerializer
SerializerAssemblyGenerator
SerializerCodeGenerator
*Attribute
AbstractSerializers.*
GeneratedSerializers.*
[Un]packHelpers
One More Things
MessagePackObject
TypeInfoEncoder
SerializationTarget SerializationMember
AbstractSerializers.*
GeneratedSerializers.*
[Un]packHelpers
Packer
Unpacker
One More Things
SerializerBuilder
設計コンセプト
• コンテキスト
• 特別扱いの許容
• 多くの値型、コレクション型、Null許容型、Tuple
• コード生成
コンテキスト
• キャッシュと設定を保持するインスタンス
• そこを見れば必要なものはある
• DefaultContext
• 多くの場合、コンテキストインスタンスの作成や管理はしたくない
• 既定のインスタンスをグローバルに用意(使用は完全に任意)
• ここに依存はしない(No God)
特別扱いの許容
• 単一の汎用実装は魅力的だが、複雑すぎる
• 特別扱いするとある意味で楽
• 値型(Nullable<T>を含む):多くの場合、メンバーごとのマッピン
グよりも独自表現が適切(例:DateTime)
• コレクション:あえて内部構造にアクセスしないことで汎用化(例:
リンクリストや循環キュー)。GetEnumeratorとAddを使用
• 文字列/バイト列:IEnumerableだが直接マッピング可能、エンコー
ディング
• Tuple:意味論的にヘテロ型配列
コード生成
• リフレクションはあまり速くない
• 実行時のボックス化を避けたい
• ソースコードが吐けると無理が効く
• ビルドプロセスに組み込める
• ステップ実行できる
コード生成の問題
• 技術ごとにサポートするプラットフォームが異なる
• Silverlightは?
• ※開発当初はSilverlight3/4がまだ息をしていた
• WinRTは?
• AOT をフォローできるか(事前生成できるか)
• strategy patternで逃げる
• プラットフォームに合わせた実装
• 無理のない範囲で抽象化
コード生成のリストラクチャリング
• LCG:Silverlightの旧バージョンと共にさようなら
• ExpressionTree:Silverlight5とWin8 SLの不人気と共にさようなら
• 式ベースなので、他の技術(Reflection.EmitやCodeDOM)との差異が著し
くつらかった
• ここはリフレクションでも十分だと考えた
• CodeDOM:更新停止、.NET Core非対応
• RoslynのSyntaxFactoryに絶賛移行中
• そもそもコード生成しなくてもいい部分はコード生成しない
• PackHelpers、UnpackHelpers
XPLAT
実装
テスト
パッケージング
XPLAT実装の一般論
• XPLATにできないところ
• プラットフォーム依存の強いところ
• I/O、スケジューリング等
• 「OSの色が出るところ」:GUI 等
• とはいえ、I/OやスケジューリングはランタイムBCL/FCLやPALが吸
収
• アルゴリズムやインメモリデータ構造はXPLATにしやすい
XPLATにできそうもないところ
Type Introspector
Encoder/DecoderCache
Validator
API Type Model
Mapper
SerializationContext
MessagePackSerializer
SerializerAssemblyGenerator
SerializerCodeGenerator
*Attribute
AbstractSerializers.*
GeneratedSerializers.*
[Un]packHelpers
One More Things
MessagePackObject
TypeInfoEncoder
SerializationTarget
One More Things
SerializerBuilder
SerializationMember
AbstractSerializers.*
GeneratedSerializers.*
[Un]packHelpers
Packer
Unpacker
✔
✔
✔ ✔
✔
✔ ✔
✔
✔
✔
悪魔は細部に宿る
• コード生成は前述のとおりポータビリティがない
• リフレクションAPIには、実はポータビリティがない
• 過去のAPI設計の誤りの清算がしたかった?
• SILVERLIGHTとかUWPとか
• .NET Standard 1.5ならだいぶまし
.NET Platform Standardで解決
• しない
• .NET Fx 3.5、Silverlightは対象外(Unityもねは解決しそう)
• .NET Standard 1.5なら解消とはいうが
• .NET 4.6.2 or .NET Core 1.0以上で「.NET Standard 1.5」
• .NET Standard 2.0に対応する.NET Fxは4.6.1から
AOT
• JITとは異なるところがまだ多い
• Reflection.Emitとかできない
• シリアライザーは特殊なので秘孔をつきやすい(?)
• constrained.コールで死ぬ
class Foo<T> where T : IDictionary<K,V> {
int Bar(T dic) => dic.Count // ExecutionEngineException
}
• 複雑めのジェネリック引数で死ぬ
• DllImportしているCRTが引っかかる
現実
Type Introspector
Encoder/DecoderCache
Validator
API Type Model
Mapper
SerializationContext
MessagePackSerializer
SerializerAssemblyGenerator
SerializerCodeGenerator
*Attribute
AbstractSerializers.*
GeneratedSerializers.*
[Un]packHelpers
One More Things
MessagePackObject
TypeInfoEncoder
SerializationTarget
One More Things
SerializerBuilder
SerializationMember
AbstractSerializers.*
GeneratedSerializers.*
[Un]packHelpers
Packer
Unpacker
✘
✔
✔✘
✘
✘
✘
✘
✘
サポートできないAPI
Collections.Concurrent.*
Threading.*
TypeCode
Reflection.*
Reflection.*
AOT
Reflection.*
✔
Reflection.*
CodeDom.*
解決策
• プロジェクトを分ける
• Net3.5/4.5/4.6、Unity(corlib only/Full)、Xamarin.Android、
Xamarin.iOS、
Silverlight5/8、.NET Standard 1.1/1.3
• #if
• 意外と組み込みのコンパイラー定数がある(SILVERLIGHT、UNITY_xx、
NETSTANDARD1_x)
• NETSTANDARD1_X、.NET Core Dev Tools Previewのcsprojだと組み込まれてない
ので注意
• きっとみんな持ってるReflectionAbstractions
コードの例
テスト
• シリアライザーは幸いにもテストの自動化が容易
• 副作用がない
• 手でテストとかやってられないというより、出来ない
• 入浴中や食事中でも実行できる
テストのやり方
• パターンと境界値によるケースの抽出、実行
• 型
• コード生成の種類
• 組み合わせ・入れ子のパターン
• 本当はここは間引くべき
• 今はT4でコードを生成
• 一番パターンの多い環境で18,345メソッド、少ない環境だとそ
の半分くらい
• .NET 3.5、.NET 4.6、WinRT、WP8、Unity.iOS、Xamarin.iOS
テスト環境
• メイン
• デスクトップPC
• iOS ビルド+テスト
• Mac mini
• iPad mini
• CI
• AppVeyor(OSS plan)
テストツール
• フレームワーク
• NUnit
• NUnitLite(Xamarin)
• NUnitLite ベースのツール(Unity)
• UniRx のツールを移植・改造
• MSTest(WP、UAP)
テスト対象プラットフォーム
プラットフォーム テスト環境
Xamarin Android NUnitlite
Xamarin iOS NUnitlite
.NET Standard 1.1 MSTest
.NET Standard 1.3 MSTest
.NET Standard 1.3 + AOT* MSTest
プラットフォーム テスト環境
.NET 3.5 NUnit
Unity(mscorlib only) n/a
Unity NUnit + ツール
.NET 4.5 n/a
.NET 4.6 NUnit
Silverlight 5 n/a
Windows Phone 8 Silverlight MSTest
* .NET Standard 1.3 の UAP プロジェクトを
テスト用に作り、ネイティブコンパイルして実行
テストの例
ビルドとパッケージング
• .nuspecは手書き
• ビルドは簡単なps1スクリプトで
• PowerShell Coreで動くのかどうかは未検証……
• 基本はMSBuild(というか.sln)べったり
• .NET用.sln
• .NET 3.5用.sln
• Xamarin用.sln
• WinRT/WP/Silverlight系用.sln
• 最近はAppVeyorにお任せ
ビルドの例
Q&A

Net fringejp2016

Editor's Notes

  • #11 ・PSD データのシリアル化を JSON と比較 ・dotnet run でも動かしてみる
  • #35 プロジェクト構成(最新の master) ReflectionAbstractions.cs CollectionSerializer.cs
  • #41 T4 の例と、MSTest 切り替えの例 iPad Mini で Unity テスト
  • #43 PS1 スクリプトの例 AppVeyor の例