Xamarin.FormsとMVVMに触れてみたい話
はじめに
この記事は [初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 - Qiita の四日目の記事です。
前回に引き続き Xamarin.Forms に触ってみたお話ですが、今回は MVVM にも入門してみた話を中心にまとめます。
MVVMについて
Xamarin について調べると必ずと言っていいほど登場する MVVM について。
詳しくは参照サイトを見ていただくとして超おおざっぱにまとめると、
プログラムを下記の3つに分けて設計しましょう、という話ですね。
- View: 画面に表示する部分を担当する。ボタンクリックなどのイベントを検知して ViewModel に伝える。
- ViewModel: 1.View と、3.Model とをつなぎ合わせる。通常 1.View と一対の関係となる。
Model: 計算処理などを行う。処理の内容によってクラス数は増減する。
1.View -> 2.ViewModel -> 3.Model の順に呼び出され、矢印の先にあるクラスのことを知ることはできるが、
逆はできない(例: 1.View は 2.ViewModel を知っていて呼び出すことができるが、逆は×)
なるほどね。完全に理解した(わかってない)。という感じなのですが、特に気になったことが2つありました。
Viewをコントロールするものは誰か
実はこの MVVM を Unity でも試してみたのですが、その時に迷ったのがコレ。
- View: uGUI の Canvas
- ViewModel: ViewModel の機能を担当するクラス
- Model: Model の機能を担当するクラス
とすると、誰が 1.View の表示・非表示を切り替えるの?また、ページを表示した直後の処理って誰がするの?という話です。
よくよく見てみると、 Xamarin.Forms で View を担当するのは、 (例えば) MainPage.xaml だけではなく、
MainPage.xaml.cs がいます。
Android でみると Activity ですね。
またページ遷移を担うのはまた別のクラス(例えば NavigationPage に関連するクラス)です。
これらをすべて View としてまとめてしまって良いのか疑問はありますが、
Canvas や Xaml の表示・非表示やページを開いた時の処理を実行するクラスは別にある、ということですね。
状態は誰が持つのか
表示だけを担う View はともかく、 ViewModel と Model の内、誰が状態を持つのか、
というのが2つ目の疑問でした。
結論としては、 View に関連する状態は ViewModel が、 Model の各処理に関連する状態はそれぞれのクラスが持つ、
ということのようです。
考えてみれば処理の中心となるクラスが情報を持ち、それ以外のクラスへは必要な情報だけを渡す、
というシンプルな考え方と言えそうです。
INotifyPropertyChangedによる通知
さて、 2.ViewModel は 1.View を知ることはできない、と書きましたが、
例えば クリックイベント発火 -> 処理 -> 完了後に表示を切り替え としたい場合は、
View に処理が完了したことを伝えたくなります。
View で Obervable を使うなどなど方法は色々ありますが、
INotifyPropertyChanged を使う方法も便利だと思いました。
呼ぶ側 (View)
private SubPageOneViewModel viewModel; public partial class SubPageOne : ContentPage { viewModel = new SubPageOneViewModel(); // ViewModelからの通知を購読. viewModel.PropertyChanged += (_, e) => { // 処理完了後に何かする. }; } publci void OnDoSomethingButtonClicked(object sender, EventArgs e) { viewModel.DoSomething(); }
呼ばれる側 (ViewModel)
private string calcResult; public string CalcResult { get => calcResult; set { // 値をセットするときに購読者に通知を送る. calcResult = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CalcResult))); } } public void DoSomething(){ // 何か計算処理. // CalcResult の set が実行される. CalcResult = "計算結果"; }
- 上記では CalcResult に全く同じ値を代入した場合も通知が送られるため、
set で必要に応じて不要な通知が送られないようにします。 - ViewModel 側では プロパティではなく変数 calcResult に値を入れることもできますが、
その場合通知が送られないので注意が必要です。
DataBindingってみる
MVVM によるクラス同士の疎結合化を助けてくれる機能の一つに DataBinding があります。
xaml.cs クラスでボタンなどのインスタンスを持つことなく、
Model で変更された値をそのまま View (xaml) に反映したり、クリックなどのイベントを直接取得することができます。
SubPageOne.xaml
< ?xml version="1.0" encoding="utf-8" ?> < ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="XamarinSample.View.SubPageOne"> < ContentPage.Content> < StackLayout BackgroundColor="#0078d7"> < Label Text="{Binding CalcResult}" /> < Button BackgroundColor="Yellow" Command="{Binding CalcCommand}">< /Button> < /StackLayout> < /ContentPage.Content> < /ContentPage>
- クリックイベントとして、 Clicked= に DataBinding を設定することはできず、
ICommand を使ってコマンドとして登録する必要があります(コマンドについては後述)。
SubPageOne.xaml.cs
using Xamarin.Forms; using XamarinSample.ViewModel; namespace XamarinSample.View { public partial class SubPageOne : ContentPage { public SubPageOne() { InitializeComponent(); // ViewModel クラスを DataBinding として設定. BindingContext = new SubPageOneViewModel(); } } }
と、ここまでは良かったのですが。。。
失敗
最初、 DataBinding する値を下記のように書いていました。
SubPageOneViewModel.cs
using System.Windows.Input; using Xamarin.Forms; namespace XamarinSample.ViewModel { public class SubPageOneViewModel { public ICommand CalcCommand { get; private set; } public string CalcResult{ get; set; } public SubPageOneViewModel() { CalcResult = "Starts"; // ボタンに Binding しているコマンド. CalcCommand = new Command(() => { var subtraction = DependencyService.Get(); CalcResult = subtraction.Calc(0, 1).ToString(); }); CalcResult = "Start2"; } } }
実行してみるとエラーは発生せず、ラベルには「Start2」と表示され、
ボタンを押すと CalcCommand の中の処理が実行されます。
しかし、なぜか CalcCommand の中にある「CalcResult = subtraction.Calc(0, 1).ToString();」がラベルに反映されないorz..
(なお「CalcResult = "Start2";」は実行されているのもよくわからず。。。)
Android などではイベントが別スレッドで発火するため、
下記のようにメインスレッドで実行してみては...?と思いましたが、うまくいかず。
// これでも反映されず. CalcCommand = new Command(() => { Device.BeginInvokeOnMainThread(() => { var subtraction = DependencyService.Get(); CalcResult = subtraction.Calc(0, 1).ToString(); }); });
実は、 DataBinding で変更された値を View に反映するためには、
INotifyPropertyChanged で変更を通知する必要があるのでした。
SubPageOneViewModel.cs
using System.ComponentModel; using System.Windows.Input; using Xamarin.Forms; namespace XamarinSample.ViewModel { public class SubPageOneViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public ICommand CalcCommand { get; private set; } private string calcResult; public string CalcResult { get => calcResult; set { calcResult = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CalcResult))); } } public SubPageOneViewModel() { CalcResult = "Starts"; CalcCommand = new Command(() => { var subtraction = DependencyService.Get(); CalcResult = subtraction.Calc(0, 1).ToString(); }); CalcResult = "Start2"; } } }
また、 PropertyChanged を実行するときの引数「PropertyChangedEventArgs(nameof(CalcResult))」は Binding しているプロパティ名を渡す必要があり、
例えばローカル変数である「PropertyChangedEventArgs(nameof(calcResult))」などでは反映されないようなので注意が必要です。
DataBinding を使うことで、 xaml.cs クラスからほとんど処理を省くことができ、
表示する部分と処理を実行する部分とがより簡単に分離できるようになりました。
おわりに
まだ慣れないせいか、 xaml や xaml.cs と実行される処理が(自分で書いたコード上では)切り離されているのは、
どこかふわふわして不安な感じもあります。
ただ、だからこそ仕様の変更に強いコードになる、ということだとは思うので、
ゆっくり内容を理解していきつつ、使いどころや効果的な使い方を模索していきたいと思います。
参照
Xamarin
MVVM
- THE MODEL-VIEW-VIEWMODEL (MVVM) DESIGN PATTERN FOR WPF
- MVVMのModelにまつわる誤解 - the sea of fertility
- 今さら入門するMVVMに必要な技術要素(Xamarin.Forms & UWP) - かずきのBlog@hatena
DataBinding
【C#】処理の委譲で迷った話
はじめに
この記事は C# Advent Calendar 2017 の一日目の記事です。
「継承より委譲を」という言葉を、Java開発者の方々を中心に(と思う)よく目にします。
- クラスの継承をすると、親クラスの変更に子クラスが大きな影響を受けるので変更がしづらくなる
- 委譲の場合、外部から呼び出しできるメソッド・そうでないメソッドの区別をつけやすい
- 一般的には子クラスが親クラスの特別な種類である場合(is-a関係)に継承し、
親クラスが子クラスを含んでいる(例えば子クラスが持つ機能を親クラスが持っている状態。 has-a関係)場合は委譲を用いる
といったところがその理由のようです。
OK。じゃあ処理を委譲しましょう。
と思ったのですが、どのように設計すれば委譲したことになるの?というところで迷ったので、
その辺りをまとめます。
delegateについて
「C# 委譲」といったキーワードで検索すると、 delegate についての記事が見つかります。
delegate はごく簡単にまとめるとメソッドを引数として渡せるものです。
// 引数、戻り値が同じメソッドを変数として扱うことができる. public delegate void CallNoArgMethod(); public class MainController { public MainController() { var noArgMethod1 = new CallNoArgMethod(NoArgMethod); var noArgMethod2 = new CallNoArgMethod(StaticNoArgMethod); // NoArgMethod()が実行される. noArgMethod1(); // StaticNoArgMethod()が実行される. noArgMethod2(); // delegateメソッドをひとまとめにすることもできる. var mixedMethod = noArgMethod1; mixedMethod += noArgMethod2; // NoArgMethod()とStaticNoArgMethod()が実行される. mixedMethod(); // delegateを自分で定義せずに Action 、Func (戻り値あり)を使うこともできる. var action = new Action(NoArgMethod); // 戻り値の型を指定する必要がある. var funcWuLongTea = new Func(CallHasReturnValueMethod); } // delegateを作るメソッド public void NoArgMethod() { Debug.WriteLine("世界さん、ちーっす"); } // staticメソッドでも同じように扱うことができる. public static void StaticNoArgMethod() { Debug.WriteLine("staticだよ"); } public string CallHasReturnValueMethod() { return "Hello"; } }
一般的には処理が完了したあと呼び出し元に通知するためのコールバック、ボタン押下などのイベント、
Linqなどで用いられる無名関数(通常ラムダ式が使われますが)などで使用されます。
var nums = new List{1, 2, 3, 4, 5}; // delegateを使って書く. var evenDelegate = nums.Select(delegate(int num) { return num % 2 == 0; }); // ラムダ式を使って書く. var evenLambda = nums.Select(num => num % 2 == 0);
で、これを使ってどうやって委譲するんです?というか、処理を委譲するのにdelegateを使う必要ってあるんです?
はい。ここで迷いました...orz
前述の通り、委譲というのは(例えば)そのクラスが持つ機能を別のクラスに分割する(委譲する)ことらしい。
ただ、これって別に delegate を使う必要はなくて、
処理を別クラスに分けて、メソッドを呼び出してあげれば良いのでは......?
と思ったのですが、あまりに自明すぎるのか、誰も気にしていないのか、この辺りに言及する資料は見つけられませんでした
(日本語で検索した限りでは)。
元の言葉を調べてみよう
ところで、Java界隈で継承と委譲の話が良く出てくる理由に、 Effective Java があるようです。
では、英語でも検索するにあたって、英語版の Effective Java ではどう表現されているかを見てみることにしました。
「Favor composition over inheritance」
ふむふむ。「inheritance」が「継承」なので、委譲にあたる言葉は「composition」のはずですね。
「composition」の意味を調べると「組み立て、合成」など(compositionの意味 - 英和辞典 Weblio辞書)と出てきます。
ほうほう。
......。
「継承より委譲を」の時の「委譲」って「委譲」じゃないんじゃないですか???
......少々取り乱しました。
結局、やるべきことは一つのクラスに対象物が持っている性質や機能をひとまとめにするのではなく、
複数クラスに切り分けて親となるクラスから呼び出すようにしましょう、ということで、
その方法についてはメソッド呼び出しでも delegate でも良い、という理解で良さそうです。
delegateで遊んでみる
せっかくなのでここからは delegate を使ってもう少し遊んでみることにします。
コールバック
何かの処理を行った後、それが完了したことを呼び出し元に伝えてほしい場合があります。
delegateを使うことで、呼び出された側が呼び出し元のクラスインスタンスを持たなくても通知できるようになります。
public void StartControlling() { ExecuteSomeOperationWithCallback(() => { // 完了後の処理. }); } private void ExecuteSomeOperationWithCallback(Action callback) { // 何か処理して終わったらdelegateメソッド呼び出し.. callback(); }
呼び出せる処理を限定する
delegate でメソッドを変数として扱うには、クラスのインスタンスが必要になります。
ただ、クラスのインスタンス自身は、delegate メソッドの変数(メンバー変数として作成)を生成するときにローカル変数として生成しても、
その後で delegate メソッドがGCで空になる、といったことは無いようです。
そのため、呼び出し元のクラスで呼び出すメソッドを限定することができます。
public class MainController { private CallStringArgMethod callMethod; public MainController() { var delegateMethodManager = new DelegateMethodManager(); callMethod = new CallStringArgMethod(delegateMethodManager.PrintMessage); } public void StartControlling() { // このクラスで呼べるのはPrintMessageだけ. callMethod("Hello"); } } public delegate void CallStringArgMethod(string message); public class DelegateMethodManager { public void NoArgMethod() { Debug.WriteLine("Hello No Arg"); } public void PrintMessage(string message) { Debug.WriteLine(message); } }
その他
例えばUnityで3Dモデルを動かす、という処理を複数クラスで共通化させた場合。
そのままの名前で呼び出すのではなく、 moveCube 、 carModel など対象物に特化させた名前を付けると、
意図がよりわかりやすくなるかもしれません。
また、メソッド自体は private にしておきたいが、特定の条件でのみ操作したい、といった場合、
intなどの変数でやるように Getter / Setter を使うことができます。
おわりに
一般的に浸透している(と思う)言葉でも、語源に近い言語で調べてみるの大事ですね(苦笑)。
まぁそれはそれとして、 delegate もコールバック関数やLinqで使う以外にも、
使い方によってはなかなか便利そうです。
もちろん乱用は厳禁ですが(;'∀')
明日は neuecc さんです。よろしくお願いいたします(..)_
参照
Xamarin.Formsに触れてみた話
はじめに
この記事は [初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 - Qiita の一日目の記事です。
なんとなく気になりつつも触れていなかった Xamarin に触れてみた、という内容です。
なお Xamarin には Android や iOS の薄いラッパーである Xamarin.Android / Xamarin.iOS もありますが、
今回は Xamarin.Forms を試してみることにしました。
理由としては、同じように C# で書けてマルチプラットフォームに対応する Unity との使い分けができると良いな、と思ったためです。
Xamarinプログラミング入門をベースに自分の興味に任せて試した内容をまとめてみたいと思います。
準備
まずは準備から。 Visual Studio は 2017.15.4.4 Community Edition を使っています。
インストールは Visual Studio 本体のインストール時か、メニューの ツール > ツールと機能を取得 から、
.Netによるモバイル開発 にチェックを入れると可能です。
簡単ですね。
Xamarin Live Playerが有効にならない
Xamarin には Xamarin Live Player という機能があり、
Android や iPhone に同名のアプリをインストールして Visual Studio とリンクしておくと、
同一ネットワークにつながっている場合はUSBケーブルでPCに接続しなくても実機でのデバッグを行うことができます。
(iPhone は Mac が必要)
使い方は例えば Android なら、プロジェクトを Android に切り替えて、
デバイスを選択するところから Xamarin Live Player を選択・・・
できませんでした/(^o^)\
Visual Studio のバージョンは問題ないし、 Xamarin も最新といっているし・・・
と思っていたら、公式サイトに載っていました。
どうやらデフォルトでは有効になっていないらしく、
ツール > オプション > Xamarin > その他 から、 Xamarin Live Playerを有効にする にチェックを入れる必要があるそうです。
ちゃんと説明は読みましょう、というお話でしたorz
なお有効にしたあと、テザリング環境でも試してみましたが、
Androidについてはテザリング環境でも問題なく接続できました。
確認したい端末が複数台ある場合など、結構便利なのではないでしょうか。
デフォルトのプロジェクトを見てみる
テンプレートを Blank App 、 Code Sharing Strategy を PCL にして Xamarin プロジェクトを作成すると、
以下の4つのプロジェクトを持つソリューションが生成されます。
(プロジェクト名は XamarinSample としました)
View は XamarinSample (移植可能) のものを使います。
が、ほかのプロジェクトを見てみると、それぞれ MainPage.xaml(UWP) や MainActivity.cs(Android) など、
スタートアップやメインページの表示に関わりそうなファイルが見つかります。
とりわけ気になったのが UWP 。
MainPage.xaml と App.xaml って、 XamarinSample (移植可能) と一緒じゃないのという。
試しに UWP の MainPage.xaml にボタンなどを追加してみましたが、
少なくともデフォルトでは反映されませんでした。
また、 MainPage.xaml を削除するとエラーが発生しました。
基本的に各プロジェクトのコードはスタートアップのために存在するもののようで、
UWP については MainPage.xaml.cs や App.xaml.cs のコードビハインドが必要なので空の Xaml ファイルがある、
ということのようです。
xamlとxaml.csについて
XamarinSample (移植可能) の MainPage.xaml と MainPage.xaml.cs ですが、
デフォルトではプロジェクト直下にあります。
これを View というディレクトリを作ってその中に移動し、
MainPage.xaml.cs の namespace を合わせて XamarinSample.View のように変更してやると、エラーになります。
これは MainPage.xaml や App.xaml.cs で MainPageクラスを呼んでいるためで、
合わせて変更してあげる必要があります。
MainPage.xaml
< ?xml version="1.0" encoding="utf-8" ?> < ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="XamarinSample.View.MainPage"> ~省略~ < /ContentPage>
App.xaml.cs
~省略~ public App() { InitializeComponent(); MainPage = new XamarinSample.View.MainPage(); } ~省略~
イベントとナビゲーションの追加
それでは、画面にボタンを追加して別のページに遷移する、というのを試してみたいと思います。
まずは SubPageOne.xaml というページを追加しておきます。
ナビゲーション
Xamarin.Forms では、 NavigationPage を使用することで、
比較的簡単にページ遷移が実装できるようになります。
NavigationPage を使用するためには、
XamarinSample (移植可能) の App.xaml.cs を変更する必要があります。
Before
~省略~ public App() { InitializeComponent(); MainPage = new XamarinSample.View.MainPage(); } ~省略~
After
~省略~ public App() { InitializeComponent(); MainPage = new NavigationPage(new XamarinSample.View.MainPage()); } ~省略~
あとは下記のように Navigation.PushAsync を使えばOKです。 (なお遷移後のページには戻るボタンが自動で表示されます)
MainPage.xaml.cs
using System; using Xamarin.Forms; namespace XamarinSample.View { public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); } async void OpenSubPageOneButtonClicked(object sender, EventArgs e) { // 遷移後のページ(SubPageOne.xaml.cs を指定する). await Navigation.PushAsync(new SubPageOne()); } } }
遷移前
遷移後
イベント
MainPage.xaml にボタンを追加して、 MainPage.xaml.cs の OpenSubPageOneButtonClicked をイベント関数としてセットします。
MainPage.xaml
< ?xml version="1.0" encoding="utf-8" ?> < ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="XamarinSample.View.MainPage"> < ContentPage.Content> < AbsoluteLayout BackgroundColor="Yellow"> < Button AbsoluteLayout.LayoutBounds="20,20,200,30" Clicked="OpenSubPageOneButtonClicked" BackgroundColor="#0078d7">SubPage1< /Button> < /AbsoluteLayout> < /ContentPage.Content> < /ContentPage>
プラットフォーム固有のクラスを呼ぶ
さて、基本的には各プラットフォーム共通の処理を XamarinSample (移植可能) に追加していくわけですが、
中にはプラットフォームごとに処理を分ける必要がある場合もあります。
その場合の方法はいくつかあるようですが、
今回は DependencyService を使うことにしました。
XamarinSample (移植可能) にインターフェースを作っておき、
各プラットフォームのプロジェクトにそのインターフェースを継承したクラスを作り、
そこで固有の処理を書きます。
で、それを DependencyService を使って実行時に該当プラットフォームのクラスを呼ぶ、
という流れのようです。
ICalc.cs
namespace XamarinSample { public interface ICalc { float Calc(float currentValue, float calcValue); } }
呼ばれる側
Subtraction.cs (Androidプロジェクトに作成)
using Xamarin.Forms; using XamarinSample.Droid; // DependencyServiceで呼べるようにする. [assembly: Dependency(typeof(Subtraction))] namespace XamarinSample.Droid { public class Subtraction : ICalc { public float Calc(float currentValue, float calcValue) { return 4; } } }
Subtraction.cs (UWPプロジェクトに作成)
using Xamarin.Forms; using XamarinSample.UWP; // DependencyServiceで呼べるようにする. [assembly: Dependency(typeof(Subtraction))] namespace XamarinSample.UWP { public class Subtraction : ICalc { public float Calc(float currentValue, float calcValue) { return 2; } } }
呼び出す側
SubPageOne.xaml.cs
using System.Diagnostics; using Xamarin.Forms; using XamarinSample.ViewModel; namespace XamarinSample.View { public partial class SubPageOne : ContentPage { public SubPageOne() { InitializeComponent(); var subtraction = DependencyService.Get(); Debug.WriteLine(subtraction.Calc(0, 1).ToString()); } } }
これで、Android で実行した場合は 4 が、Windows で実行した場合は 2 が返ってきます。 #if ~ で切り分けるよりシンプルで良いですね。
出力する
apk ファイル (Android) や ipa ファイルとしてアプリを書き出したい場合、 ソリューションエクスプローラーのそれぞれのプロジェクト上で 右クリック -> アーカイブ をクリックすればOKのようです。
UWP は HockeyApp または ストア > アプリパッケージの作成 から。
また Windows で iOS を出力する場合は、 Mac の Xcode に接続されている必要があります。
なおストア配信時の設定は下記のような情報を参考に。
- Xamarin.Android で作成したアプリケーションの配布方法 - Xamarin : XLsoft エクセルソフト
- Xamarin.iOS で作成したアプリケーションの App Store への配布方法 - Xamarin : XLsoft エクセルソフト
おわりに
同じように C# でコードを書き、マルチプラットフォームにアプリを作成できる Unity とはずいぶん違うのだなぁ、というのが率直な感想です。
C#7が使えたり (Unity は Experimental な機能を On にしても C#6 までの対応 )、
独自のお作法ももちろんあるのでしょうが、 WPF など普通の? C# に近い印象を受けました。
もちろん Unity が悪いって話ではなく、ただ違うという話ですよ。念のため。
Xamarin.Forms の Xaml 用 GUI エディタがない(っぽい)、
何かいじるとエラーが (大抵実行には問題がなく、エラーの出たプロジェクトをクリーン・リビルドすると直るのですが)、
とまだ発展途上なところも見受けられます。
が、すぐ改善されるだろうと思いますし、できれば自分もそれに寄与できればなぁ、とも思いました。
最後に、今回作ったサンプルは、(ボタン位置などめちゃくちゃですが)黄色をベースに作っていました。
その理由は。。。
♪ We all live in a yellow Xamarin, yellow Xamarin, yellow Xamarin ♫
https://www.youtube.com/watch?v=vefJAtG-ZKI
・・・おあとがよろしいようで。
明日は gnk263 さんです。よろしくお願いいたします(..)_
参照
- Xamarinプログラミング入門
- Xamarin.Forms 入門ガイド - Xamarin : XLsoft エクセルソフト
- Xamarin Live Player Setup - Xamarin
- Creating Mobile Apps with Xamarin.Forms Book First Edition - Xamarin
- Xamarin.Formsからプラットフォーム固有の機能を利用するには?(DependencyService利用) - Build Insider
- ios - Xamarin.iOS及びXamarin.AndroidのclassをPCLから参照する方法 - スタック・オーバーフロー
- Creating Mobile Apps with Xamarin.Forms Book First Edition - Xamarin
- Xamarin.Android で作成したアプリケーションの配布方法 - Xamarin : XLsoft エクセルソフト
- Xamarin.iOS で作成したアプリケーションの App Store への配布方法 - Xamarin : XLsoft エクセルソフト
【C#】Boxing / Unboxing ってどこで使われてるのか調べてみた
はじめに
先日 Effective C# を読んでいたのですが、その中で Boxing / Unboxing (ボックス化 / ボックス化解除)を避けましょう、という話がありました。
Boxing は雑にまとめると int などの値型を Boxing という仕組みを使って object 型にすることで、
参照型として扱えるようにする、ということです( Unboxing は object型から intを取り出す)。
OK。わかりました。値型を object 型の変数に入れないようにします(`・ω・´)ゞ
……( object 型って使わない気がするけど、どこで気を付けたら良いのだろう……?)
ということで、調べてみましたというお話です。
Boxing / Unboxing って何
Boxing / Unboxing をもう少し調べてみます。
- C# では参照型のすべてのクラスは object クラスを継承しているが、値型の構造体はそうではない。
これら2種類の値を同様に扱うためにBoxingがある。 - 値型を参照型と同じように扱いたいとき、元の値を型を持たない参照型のデータにコピーする。
こうすることで値型のデータを参照型であるかのように扱うことができる。 - object 型に加えて、 interface 型に変換する場合にも Boxing される( IComparable などジェネリクス版でないもの)
- Boxing を行うと参照型の変数が新たに生成(コピー)され、またその処理自体も重い(らしい)。
- Boxing で生成された値はあくまで元の値をコピーしたものであり、別物であることに注意が必要である。
- なお参照型はデータをヒープ領域へ、値型はデータをスタック領域へとそれぞれメモリーの違った場所に保存されるという違いがある。
ざざっと挙げてみましたが、上記のようなことのようです。
int originalNum = 9; string originalText = "9"; // Boxingされる. IComparable sampleComparable = originalNum; // Boxingされる. object sampleObject = originalNum; // Unboxing. int unboxingNum = (int)sampleObject; // Boxingされない. object sampleString = originalText;
誰がobject型を使うのか
さて、Boxing / Unboxing のことが分かったような気になったところで本題。
自分では呼んでいない気がする object 型を、誰が使っているのでしょうか。
通常こういう場合、2つのケースが考えられます。
- 昔は存在し、注意が必要だったが、言語の機能改善により見かけることはなくなった
- 自分が普段見ないところで使われている
1であれば中の人たちありがとう!めでたしめでたし! という感じなのですが、
2について調べてみることにしました。
string型の場合
例えば string クラスを調べてみます。
すると…
~省略~ public static bool Equals(String a, String b, StringComparison comparisonType); public static bool Equals(String a, String b); ~省略~public override bool Equals(object obj); public bool Equals(String value); public bool Equals(String value, StringComparison comparisonType); ~省略~
ありましたね~、 object 型。
つまり、 string.Equals を使用する場合、引数が string 型の場合は「Equals(String value)」が呼ばれ、
それ以外の型であった場合は 「Equals(object obj)」が呼ばれ、Boxingが発生する、ということですね。
なお今回は string クラスを調べましたが、 値型である float などでも同じように型が異なる場合は object 型として受け取り、
値を確認した後処理を行っています。
Boxingを避けるために
比較などを行う場合、コンパイルエラーが出る・出ないにかかわらず、
前もって型を変換しておくのが良さそうですね。
また今回はあまり取り上げていませんが、IEnumerable ではなく IEnumerable< T> のように、
ジェネリクス版が用意されているものはできるだけそちらを使う、というのも有効です。
int originalNum = 9; string originalText = "9"; // Boxingされない. bool result = originalText.Equals( originalNum.ToString()); // Boxingされない. List< int> nums = new List< int>{ originalNum };
Boxingを見つける
Boxing されているかを調べる方法はないのでしょうか。
実は、IL (中間言語) では Boxing されるときに box と表示されます。
分かりやすい!
なお IL は、 ReSharper を使う場合、メニューのReSharper > Windows > IL Viewer から表示できます。
(事前にビルドしておく必要あり)
計測する
それでは最後に、遅いと話題の Boxing が本当に遅いか、 Unity の Profiler を使って調べてみます。
計測するコードはこちら。
public Button BoxingButton; public Button CastButton; private void Start () { BoxingButton.onClick.AddListener(() => { Profiler.BeginSample("PerformanceSampling Boxing"); var results = GetResultsWithBoxing(); Debug.Log(results.Count); Profiler.EndSample(); }); CastButton.onClick.AddListener(() => { Profiler.BeginSample("PerformanceSampling Cast"); var results = GetResultsWithCasting(); Debug.Log(results.Count); Profiler.EndSample(); }); } private ListGetResultsWithBoxing() { var results = new List (); var sampleText = "99"; var seed = Environment.TickCount; for (var i = 0; i <= 100000; i++) { var random = new System.Random(seed++); results.Add(sampleText.Equals(random.Next(1000))); } return results; } private List GetResultsWithCasting() { var results = new List (); var sampleText = "99"; var seed = Environment.TickCount; for (var i = 0; i <= 100000; i++) { var random = new System.Random(seed++); results.Add(sampleText.Equals(random.Next(1000).ToString())); } return results; }
詳しい計測方法はこちら。
で、その結果がこちらです。
・・・ん? Boxing してる方がむしろ速いんじゃね・・・(。´・ω・)?
どうも、今回のコードでは Boxing にかかるコストより ToString() にかかるコストが大きかったっぽいですね。。
という訳で、やはりパフォーマンスを向上するには計測が必須、ということのようですね(白目)。
おわりに
という訳で、結局何が言いたいのか分からない内容となりましたが、
普段何気なく使っている関数も、どのように実行されるのかをしっかり追いかけるのは重要ですね。
また、パフォーマンス向上のためには、しっかりと計測して対策が有効かどうかを検証するのも大事ですね。
あと、「なんとなく」やっていた処理の裏側をちょっと覗いてみる、というのはとても楽しいものですね(*´ω`)
参照
Visual Studio 2017 + ReSharper で Unity (C#) のスクリプトを書こうとして引っかかったこと ( namespace 編)
はじめに
普段 Unity のスクリプトを書くときは JetBrains Rider を使っているのですが、
Visual Studio + ReSharper だと IL Viewer があるし便利やな~、と思ったので、そちらも試してみることにしました。
ら、いくつか不便なところがあったのでメモっておくことにします。
課題
今回引っかかって直したかったのは以下です。
- VisualStudio からクラスを追加すると namespace が Assets.Scripts のように設定されてしまう
( Assets/Scripts より下の階層だけを namespace として扱いたい) - namespace から Assets.Scripts を削除すると、 ReSharper が Warning を出す
- ソリューションエクスプローラーからプロジェクトのプロパティが開かない
特定の namespace を無視する
実は解決したとは言い難いのですが。。。
VisualStudio の 追加 > クラス からクラスを追加すると、下記のようなファイルが追加されます。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Assets.Scripts { class Class1 { } }
問題は「namespace Assets.Scripts」の部分で、 Assets/Scripts より下にある階層だけを namespace として扱いたい訳です。
が、その設定がわからずorz
後述しますが、プロジェクトのプロパティを開いて、「既定の名前空間」を確認しても空になっていました。
対処法
で、どうしたかというと、 追加 > クラス からクラスを追加するときに、
クラス ではなく CSharp MonoBehaviour を追加するようにしました。
そうすると、UnityEditor からファイルを追加したときと同じように namespace 無しの状態でクラスが作成されます。
その状態から namespace を必要に応じて追加するか、
もしくはあきらめて Assets.Scripts を削除する(白目)
ま、まぁそんなにクラスを追加しまくらないですよね?ね?
Rider では普通の C# クラスを追加しても Assets.Scripts を含まない namespace が挿入されるので、
もう少しちゃんとした解決方法が見つかったら追記します。
warning を止める
ReSharper をインストールしている場合、手動で namespace を変更したとしても「namespace Assets.Scripts になってませんよ?」と警告が出されます。
これを防ぐためには、ソリューションエクスプローラー上で Assets 、 Scripts のフォルダを選択し、 プロパティの ReSharper > Namespace Provider を False に変更します。
これで例えば Assets/Scripts/Sample というフォルダ構成だった場合、
namespace Sample としてくれます。
プロジェクトのプロパティを開く
UnityEditor から生成した .sln ファイルを Visual Studio 2017 で開くと、
なぜかソリューションエクスプローラー上のプロジェクト名で右クリック > プロパティを選択してもプロパティ画面が開きません。
なんぞこれ?としばらくググっていたのですが、どうもプラグインとして入れている Tools for Unity が原因のようです。
プロパティ画面を開くことができるようにするには、 ツール > オプション > Tools for Unity > 全般 > その他 で、
プロジェクト プロパティへのアクセス という項目があり、これを True に変更して Visual Studio を再起動すると無事開くことができるようになりました。
おそらく下手にいじると Unity 側で問題が起きる、ということなのかもしれませんが、
せめてメッセージぐらい出してくれても。。。(見落としてるだけかもですが)
おわりに
Unity のスクリプトを書くときは、 やはり Rider が使いやすいなぁとは思うのですが、
C# を書くという点では、 Visual Studio や ReSharper の方が歴史も長く、使いやすい場合もあります。
両方をうまく使いこなしてバリバリコードを書いていきたいところですね( *´艸`)
参照
UnityのAnimatorを今さらながら触ってみた
はじめに
まず懺悔からですが、これまでUnityでアニメーションを扱うとき、レガシーシステムのAnimationを使用していました(..)_。
とはいえそろそろこのままでもマズいってんで、Animatorを使ってみることにしました。
やりたいこと
- 3Dモデル(fbx形式でインポート)のアニメーションを再生・逆再生する
- アニメーションは3Dモデルに付与した状態でUnityにインポート
- uGUI のボタンを押したときにアニメーションを再生する
準備
まず3Dモデルを用意します。
今回はBlenderを使って、このあたりの情報を参考に簡単なアニメーションを付与しました。
インポートした3Dモデルは、 Animations タブで下記のようにフレームとアニメーション名を設定しておきます。
今回はデフォルトの状態(0 ~ 1フレーム)と、デフォルト位置から移動先への移動アニメーション(1 ~ 20フレーム)を設定しました。
Animator Controller
各アニメーションの設定は、 Animator Controller を使用することにします。
まずメニューの Create > Animator Controller で Animator Controller を作成します。
Animator ウインドウを開き、 Create State で State を3つ追加します。
その内の一つはデフォルトの状態を保持する State とします。
State を選択した状態で Inspector を開き、名前と Motion に先ほど3Dモデルに設定した StartPosition を指定します。
また、起動時に自動で実行されてほしいので、 Entry State の上で右クリック > Make Transition で、
この State に紐づけます。
オブジェクトを移動させるアニメーションは、それぞれ Motion に Moved を指定し、
逆再生(移動先から元の位置に戻す)は、 Speed を -1 に設定します。
今回は State の状態に合わせてアニメーションを再生するのではなく、
ボタン押下時に再生するので他の State には紐づけません。
アニメーションを再生する
アニメーションを再生するのは単純に Play("Animator Controller で設定した State 名") で実行可能です。
public Animator CubeAnimator; public void PlayMoveAnime() { CubeAnimator.Play("Move"); } public void PlayReturnAnime() { CubeAnimator.Play("ReturnToStart"); } public void ResetAnime() { // 再生中のアニメーションを一旦止める. CubeAnimator.enabled = false; // デフォルトのアニメーションを再生. CubeAnimator.Play("DefaultPosition"); CubeAnimator.enabled = true; }
- 一応デフォルトのアニメーションに戻す前にアニメーションを止めていますが、
今回の内容であれば無くても問題なく動作します。
アニメーションが終わったか確認する
レガシーシステムの Animation と違い、アニメーションが再生されているかを確認する IsPlaying などのメソッドは無いようです。
これを実現する方法はいくつかあるようですが、
今回は normalizedTime を見ることにしました。
CubeAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime で、
アニメーションが再生中なら 0 ~ 1 未満、再生が完了していたら1以上の値が取得できます。
なお GetCurrentAnimatorStateInfo(0) の 0 は Animator Controller のレイヤー番号です。
今回はデフォルトのまま Base Layer を使用しているので、 0 を指定しています。
using System.Collections; using UnityEngine; public class AnimeController : MonoBehaviour { public Animator CubeAnimator; public void PlayMoveAnime() { CubeAnimator.Play("Move"); StartCoroutine(CheckIsAnimeFinished()); } public void PlayReturnAnime() { CubeAnimator.Play("ReturnToStart"); StartCoroutine(CheckIsAnimeFinished()); } public void ResetAnime() { // 実行中の Coroutine を止めているが効いてない? StopCoroutine(CheckIsAnimeFinished()); CubeAnimator.enabled = false; CubeAnimator.Play("DefaultPosition"); CubeAnimator.enabled = true; } private IEnumerator CheckIsAnimeFinished() { // Play 実行直後はアニメーションが再生されていないため、 0.3 秒待ってからチェック開始. yield return new WaitForSeconds(0.3f); while (true) { // normalizedTime が 1 以上ならチェック完了. if (CubeAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1f) { Debug.Log("Finished"); break; } // 0.1 秒ごとにチェックする. yield return new WaitForSeconds(0.1f); } } }
- ResetAnime() にて、実行中の Coroutine を止めていますが、
処理が止まるより先にアニメーション終了の処理が実行されてしまうため、もう一つフラグを追加するなどの対策が必要そうです。
終わりに
今回のようにシンプルな再生・逆再生を行うだけであれば、Animation より断然シンプルに描くことができて良いですね。
ただ、アニメーションの完了をチェックするなど直接チェックできない部分もあり、
もう少し慣れないとな~、という気持ちもあります。
Animator では uGUI なども触れるようなので、 Tween 系や UniRx などを使って実装していた GUI アニメーションでも使ってみようかと思います。
参照
Blender
Unity
C# 6の String interpolation で遊ぶ
はじめに
最近ついうっかり Effective C# Edition 3 を読み始めました。
その第4項目で、 string.Format() ではなく String interpolation を使いましょう、というものがありました。
C# 6.0 から、以下のように書けるようになったとのこと(なお Unity でも C# 6.0 を使用可能にすれば問題なく動作します)。
// 今まで. string outputTextLegacy = string.Format("Hello {0}", DateTime.Now.ToString("yyyy-M-d dddd")); Console.WriteLine(outputTextLegacy); // 6.0以降. string outputTextNew = $"Hello {DateTime.Now:yyyy-M-d dddd}"; Console.WriteLine(outputTextNew);
...うむ、後者を初見だと何やってるかわからないだろうな\(^o^)/
初見だとわからないなら何度も見れば良いじゃない
という声が聞こえましたので、ちょっと触ってみることにしました。
書き方
$"Hello {DateTime.Now:yyyy-M-d dddd}"
を紐解くと、頭に $ をつけて、文字列として置き換えたい部分を {} でくくり、
フォーマットの形式は : 以降に書きます。
なおフォーマット形式がない場合はデフォルトの形式で出力されます。
また : 以降はすべてフォーマットの形式を指定する文字列として扱われるため、 } との間にスペースなどを入れるとコンパイルエラーとなります。
試す
String interpolation 扱えるものは、先程の DataTime.Now のように値をそのまま書くことも可能ですし、
変数やメソッドも使用できます。
またIDEであれば入力補完もバッチリです( Visual Studio2017 + ReSharper 、 Rider で確認)。
変数を使う
double pi = Math.PI; // 出力結果: 3.14 string outputTextNew = $"{pi:F2}"; Console.WriteLine(outputTextNew);
メソッドを使う
static void Main(string[] args) { // Mainメソッドから呼び出しているためstaticにしているが、普通のメソッドでも使用可能. string outputTextNew = $"{GetMessage()}"; Console.WriteLine(outputTextNew); } private static string GetMessage() { return "Hello"; }
- ただし当然とも言えますが、戻り値が void のメソッドはコンパイルエラーとなり使用できません。
null 合体演算子を使う
Kotlinなどでいうエルビス演算子(Nullのときだけ ?? の右側の値が使用される)も使えちゃいます。
string test = null; var outputTextNew = $"{test ?? "text is null"}";
三項演算子を使う
三項演算子を使う場合は少しだけ注意が必要です。
string test = null; var outputTextNew = $"{(test == null ? "text is null" : DateTime.Now.ToString("yyyy-M-d dddd"))}";
- 上記のように、式を () でくくる必要があります。
Linqを使う
普通に使えるようです。
ただ、あんまりやりすぎると可読性、という面から厳しそうですが。
ListtextSample = new List { "text1", "text2" }; var outputTextNew = $"{textSample.Select(t => t).Count().ToString()}"; Console.WriteLine(outputTextNew);
async / await
当然ながら使えません。
{} のエスケープ
$ をつけている場合、 {} が全て String interpolation として扱われるため、
{} をそのまま表示するにはエスケープが必要になります。
$"Hello World! {{\"text is null\"}}";
その他注意点
これも Effective C# で取り上げられている内容ですが、これを使ってSQL文を組み立ててはいけません。
SQLインジェクションを避けるため、これまで通りプレースホルダーを使用する必要があります。
おわりに
今までの方法と比較して、 String interpolation を使うことでセットする文字列の、
順番や数の間違いなどを防げる、という利点に加えて処理がグッとシンプルに書けそうなので良いと思いました。
ただ途中でも書きましたが、あまりごちゃごちゃやり過ぎると見づらくなったりバグの原因になりそうなので、
ほどほどにしておくのが良さそうですね。