Xamarin.Forms の ListView で System.MissingMethodException: Default constructor not found for type

※2016/08/31追記:今更ながら、AOT関係ないじゃんっていうツッコミもらったので、AOTっていうのを消しておきます。正しくは、単に、リンカのstrip処理ですね・・・。

突然ですが、 SIN@SAPPOROWORKS の記事を引用します:

みたいなコードを見て、 Xamarin.Forms 簡単だ!的な感じでコーディング始めよう・・・ってしてみたら、 iOS

System.MissingMethodException: Default constructor not found for type ...

みたいな例外を食らうことがあります。
これ、Xamarin.iOS でいうところの、 AOT で有るが故の制限なんですが、用は、コンストラクタが見つからないよって言われています。

そんなこと言っても、ちゃんと public で引数なしのコンストラクタは用意しているし、なんで見つからないんだよ・・・って話になるわけですが、これにはちゃんとした理由があります。

AOT のコンパイラリンカは、基本的には、コード中で利用されていない、要らないコードを最終的なプログラムから削除しようとします。これは、プログラムのサイズを小さくしようという涙ぐましい努力の一部です。

AOT のコンパイラリンカはそこそこ賢いので、この、「要らないコードを削除する」プロセスはある程度までは正確に処理ができるんですが、実は、本当は必要なコードも削除してしまうことがあります。

それはどんなときなのかというと、いわゆる、リフレクション経由の呼び出しというのがそのほとんどの場合です。

こういうクラスがあるとします:

class A
{
  public A() {}
}

次の例は、おそらく問題無くリンクできるようなパターンです:

var a = new A();

一方で、次の様な奴はダメな可能性が高いです:

var a = (A)Activator.CreateInstance(typeof(A));

このパターンは、A という型への参照は残るものの、コンストラクタへの明示的(直接的)な呼び出しがないため、コンストラクタが削除されてしまうことがあるようです。

Visual Studio で CodeLens が有効になっている場合は、もっと分かりやすい話、0 Referencesってなっているコードはヤバイです。

f:id:espresso3389:20150127041232p:plain

これ、この部分を呼び出しているコードが見つからないってことですからね。真っ先に削除の対象になってしまいます。

さて、理由は簡単なので、逆に、どっかに、こそっと、

var a = new A();

みたいなコードを挿入しておけば、それでコンストラクタがちゃんと残るわけですが、普通、そんな変なコードを入れたくはありません。

このために、 Xamarin.iOS には、 PreserveAttribute というものが用意されており、これを使って、

using Foundation; // Xamarin.iOS で PreserveAttribute を使うときにはこれが必要

...

[Preserve(AllMembers=true)]
class A
{
  public A() {}
}

とやると、このクラスのメンバは全部大事だから消さないでねっていう指示を出すことが出来ます。PCLじゃなければ。

そんなことを言っても、私は PCL を作ろうとしているので、そんな変なクラスは使えませんという場合には、

http://developer.xamarin.com/guides/ios/advanced_topics/linker/#Preserving_Code

に書かれているように、好きな namespace に自分で、 PreserveAttribute を作れば良いです。

class PreserveAttribute : System.Attribute
{
  public bool Conditional { get; set; }
}

そうして、この場合、さっきの PreserveAttribute とは微妙に使い方が違います。こいつは、削除して欲しくないメソッドなどに直接付与します。つまり、

class A
{
  [Preserve(Conditional=true)]
  public A() {}
}

みたいにして使います。

さて、本題に戻ると、さっきの SIN@SAPPOROWORKS さんのサンプルですが、

//ListViewの生成
var listView = new ListView{
  ItemTemplate = new DataTemplate(typeof (MyCell)),//セルの指定
  ItemsSource = _tweets,//データソースの指定
  HasUnevenRows = true,//行の高さを可変とする
};

っていうコードが MyCell っていうクラスを作るためのコードなんですが、これ、上記の例と同じで、明示的(直接的)なコンストラクタの呼び出しがありません。
うまく行く場合もあるので(何でか分かりません)、絶対に動作しないとは言わないのですが、死ぬ場合には、上記の通り、コンストラクタに PreserveAttribute を付与すれば直ります。

このコードの場合は、

//セル用のテンプレート
private class MyCell : ViewCell {
  public MyCell() {
...

ってなってるところを、

//セル用のテンプレート
private class MyCell : ViewCell {
  [Preserve(Conditional=true)]
  public MyCell() {
...

ってやっておくほうが安全という話です。

Xamarin、便利なんですけど、この手の???っていうトラップが多くて初心者には辛い部分もありますね。慣れると、あーまた例の奴かって感じになるわけですが。

という他人のふんどしで相撲を取るような記事でした。