SDD(Sleep-Driven Development)

睡眠の重要性!!睡眠の重要性!!

MacでC#ユーザーのRider入門〜便利機能〜

今回はRiderの便利機能の紹介

getonlyから変更通知プロパティへの変換

Xamarin.Formsをやっていると変更通知プロパティが結構出てくるのですが、もしもともとあるプロパティを変更通知可能にしようとする場合バッキングフィールド書いて〜プロパティのsettergetter書いて〜は結構手間だと思います。

Riderならそんな面倒な変換も一瞬でやってくれます! 例としてgetonlyプロパティをregion付きの変更通知プロパティに変えてみました。

f:id:crocus7724:20160918141140g:plain

Autoプロパティからバッキングフィールドのプロパティへの変換がAlt+Enterで簡単にできます!しかもバッキングフィールドの名前はプロパティの名前のアンダーバー+ロワーキャメルケースへ自動的に変換。めっちゃ便利。

Currentメソッド作成

また、よくあるかわからないけどシングルトンパターンを作るときに初回はインスタンス作成して次回以降はそれを使いまわすみたいな感じになると思いますがこれもRiderならスラスラ書けます。
Sample Current{get;}=new Sample();でいいだろという声は聞こえません

f:id:crocus7724:20160918141236g:plain

なんとnullチェックで冗長だった場合短い構文に書き換えてくれます!!素晴らしい。

Linqサポート

冗長だったやつを短いのに変えるのはnullチェックだけではありません。もしforeachで長い文を書いていた場合Linqに変換してくれます。

f:id:crocus7724:20160918141350g:plain

個人的に+=じゃなくて=が嬉しいですがまあ問題ないんでしょう。

リネーム

リネームのためだけにRiderを開いてもいいほど強力です。どのくらい強力かというと条件があうと別プロジェクトのコメントアウトした部分もリネームするか聞いてきます。

例としてバッキングフィールド付きプロパティをリネームしたとき

f:id:crocus7724:20160918141407g:plain

ちゃんとバッキングフィールドも一緒にリネームするか聞いてきます。地味に凄いですね。

Search Everywhere

これはIntelliJを使っている人ならよく知っていると思いますがShiftキーを2回押すことでいろんなものを検索します。

なんとこれクラスやシンボルだけでなく設定まで検索できます。

f:id:crocus7724:20160918142328g:plain

これでいちいちあの設定はどこだっけと悩む必要はなくなりますね!

ほかにもいろいろ便利な機能がありますがこの辺で。

とりあえずAlt+EnterとSearch Everywhereが便利すぎです。

MacでC#ユーザーのRider入門〜インストール〜

RiderとはJetBrainsが開発中のCross-platform C# IDEです。 詳しくはこちらを御覧ください。

Project Rider – 新しい C# IDE #jetbrainsrider | JetBrains ブログ

ReSharpermacOSで使えます。

ReSharpermacOS で使えます!!

(あとIntelliJなどのプラグインも使えます)

これは使うしかありません。さっそくインストールしましょう!!

注意

まずRiderは2016/9/17現在Private EAPです。上のリンクにはリリースは8月を予定と書いてある通り、当初の予定では2月にPrivate EAP、6月ごろにPublic EAP、8月か9月ころに正式リリースだった記憶があるのですが、Private EAPもギリギリまで遅れ(日本時間で2/29の24時頃だったのでギリギリセーフ(?))それから未だにPrivate EAPです。なので

  • Xamarinのプロジェクトは作れません。Xamarin Studioをお使いください。
  • Xamarinのプロジェクトを実行できません。Xamarin Studioをお使いください。
  • プロジェクト操作がかなり怪しいです(ファイル削除してもファイルが残る)。Xamarin Studioでも怪しいです。

ただし、現在のPrivate EAP10ではだいぶ使い勝手もよくC#でコーディングする分にはほぼ問題ありません。むしろReSharperが素敵すぎます。

インストール手順

まずはPrivate EAPのサブスクライバーに登録します。

以下のページの下の方で申し込みが出来ます。

www.jetbrains.com

入力が終わったらSubscribeを押します。すると2-3分以内にメールがくるのでそのメールにあるOS Xのダウンロードリンクを押します。

するとdmgファイルが落っこちてくるのでインストールします。

f:id:crocus7724:20160917200425p:plain

インストールが終了したら早速起動してみましょう。

最初にこんな画面が出てきました。

f:id:crocus7724:20160917200505p:plain

以前のバージョンなんて無いので下を選択します。

次にどちらのUIを使うかの選択肢が出てきました。

f:id:crocus7724:20160917200535p:plain

LightかDarkですね。私は断然Dark派です。

次にどのエディターカラーを使うかを選択します。左がReSharper(違和感あるけど)、真ん中がVisualStudio(違和感あるけど)、右がDarcula(というかIntelliJ)ですね。

f:id:crocus7724:20160917200555p:plain

私は初期のRiderがReSharper一択で慣れてしまったのでReSharperです。

お次はKeymapです。

f:id:crocus7724:20160917201014p:plain

Visual StudioライクかReSharperライクかIntelliJIDEAライクかが選択できます。個人的にはあとで設定から変えられるReSharper(macOS)が好きですが初期設定でも結構キーが衝突しているのでmacOSユーザーならIntelliJのキーマップがいいのかな?

お次はターミナルからRiderを起動できるようにするかです。

f:id:crocus7724:20160917201123p:plain

私は結構ターミナルを使うことがあるのでとりあえずチェックを付けときました。

最後はチュートリアルやバグ報告する場所などのリンクです。

f:id:crocus7724:20160917201141p:plain

では早速Start using Riderを押しましょう!

f:id:crocus7724:20160917201230p:plain

IntelliJプラットフォームユーザーなら見慣れた画面が出てきました。

これで準備は完了です。お疲れ様でした!!

SynchronizationContext.Currentでnullが返ってくる

躓きかけたのでメモ

C#で以下のようなメソッドを書いたとします。

//Task.Run(()=>Hoge())などで呼び出す
public void Hoge()
{
    //ネイティブUI操作
}

これを非同期(バッググラウンドスレッド)で触ろうとすると死にます。いわゆるUIスレッド以外でUI要素に触ってはいけない問題です。これを回避するにはUIスレッドで実行してやればいいだけです。

PCLの場合、呼び出し側が自分で制御できる場合は例えばTaskSchedulerクラスのFromCurrentSynchronizationContextを使えば問題ないわけですが

//var sheduler=TaskScheduler.FromCurrentSynchronizationContext();
//Task.Factory.StartNew(()=> { Hoge(); },CancellationToken.None,TaskCreationOptions.None, sheduler)で呼び出す
public void Hoge()
{
    //ネイティブUI操作
}

ライブラリ等で呼び出し側を自分で制御できない場合SynchronizationContext.Current.Postメソッドを使えばいいです。

しかしこれにも少し問題があって例えば以下のようなコードを書くと死にます。

//Task.Run(()=>Hoge())などで呼び出す
public void Hoge()
{
    SynchronizationContext.Current.Post((_=>
    {
        //ネイティブUI操作
    },null));
}

何故かと言うとSynchronizationContext.CurrentはUIスレッド以外で呼び出すとnullが返ってきてヌルポるからです。

これを回避するには必ずメインスレッド中で変数などに確保しておくのが手っ取り早いです。もしHoge()のクラスがバックグラウンドスレッド以外で初期化されるならフィールドに確保しておくのが一番簡単でしょう。

SynchronizationContext context=SynchronizationContext.Current;

//Task.Run(()=>Hoge())などで呼び出す
public void Hoge()
{
    context.Post((_=>
    {
        //ネイティブUI操作
    },null));
}

しかしクラスの初期化自体がバックグラウンドスレッドの場合はフィールドで初期化でもヌルポるかもしれません。

これを回避するには絶対にUIスレッドで実行されるところで初期化してstaticにもたせてあげればよさそうなのですが少し面倒ですね・・・

Xamarin.Forms macOSをとりあえず実行してみた

9/7、GithubのXamarin.FormsにmacOSブランチが生えました。

これでXamarin.Formsを使えばWindows(UWP)、AndroidiOSmacOSとほぼ全ての主要なプラットフォームを1ソースで書けるようになりました。やったねXamarin.Formsちゃん!対応するプラットフォームが増えたよ!!

さっそく動かしてみました。

git colne

最初にGithubからXamarin.FormsのmacOSブランチをクローンします。

Kazuki:Projects Yamamoto$ git clone -b macOS https://github.com/xamarin/Xamarin.Forms.git
Cloning into 'Xamarin.Forms'...
remote: Counting objects: 9996, done.
remote: Compressing objects: 100% (89/89), done.
remote: Total 9996 (delta 34), reused 0 (delta 0), pack-reused 9907
Receiving objects: 100% (9996/9996), 14.51 MiB | 1.73 MiB/s, done.
Resolving deltas: 100% (6314/6314), done.
Checking connectivity... done.

Xamarin Studioで実行

そしたらXamarin.Forms.slnをXamarin Studioで開きます。最初は各プロジェクトだけ開いて実行してましたがたまに起動できなくなったりして面倒くさくなったのでまとめて開くことにしました。

開いたらソリューションオプションを開き、スタートアッププロジェクトをXamarin.Forms.ContorlGallery.MacOSに変更します。

f:id:crocus7724:20160911051823p:plain

あとは実行ボタンを押せば起動できます!

f:id:crocus7724:20160911051848p:plain

うーんボタンに歴史を感じますね。あと適当にリストを選択するとクラッシュして死にます。

Demoページ

実行出来たのはいいですがデモはTwitter風でした。これはXamarin.Forms.Controls.MacTwitterDemoになります。なのでXamarin.Forms.Controls.App.csのMainPageを差し替えてあげます。

MainPage = new MacTwitterDemo();

そしてもう一度実行します。

f:id:crocus7724:20160911051934p:plain

ちゃんとデモみたいにTwitter風のページが表示されました。こっちはいい感じ。なおボタンとかは全部押せません。

ちなみにXamarin.FormsなのでiOSでもデモページを表示できます。

もう一度ソリューションオプションを開きスタートアッププロジェクトをXamarin.Forms.ControlGarally.iOSに変更して実行します。

f:id:crocus7724:20160911051952p:plain

画像が表示されてません。しかしそれ以外は表示できてるので問題無いですねハイ。

現在ブランチが生えただけですがそのうち正式にサポートされるでしょう。今は楽しみにやm・・・やりがいが増えるのを待ってます。

MacでNuGetパッケージを作成するときの注意

MacでNugetパッケージ作ってたときハマったのでメモ

nugetのバージョンは3.4.4.1321

Macでもnugetコマンドを使えばNugetパッケージを作れるのですが若干罠めいたものがあります。

結論から先に言うと.nuspecは.slnと同じ階層でパスは相対パス



では実際に問題のあるやり方をしていきましょう。

とりあえず例としてXamarin StudioでXamarin.Formsライブラリプロジェクトを作成してSampleと名づけました。

これをNugetパッケージにするときプロジェクトの真下にいろいろ広げてもいいのですが、たとえば複数プロジェクトを対象とするときは.slnと同じ階層にディレクトリを作ってそこに.nuspecなどを置く人もいると思います。

今回はtoolsというディレクトリを作り、そこでnuget specで.nuspecファイルを作成し以下のように編集しました。

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>Sample</id>
    <version>1.0.0</version>
    <authors>Yamamoto</authors>
    <licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl>
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Sample NuGet Package</description>
    <copyright>Copyright 2016</copyright>
    <tags>Sample</tags>
    <dependencies>
      <dependency id="Xamarin.Forms" version="2.3.1.114" />
    </dependencies>
  </metadata>
  <files>
    <file src="../Sample/bin/Debug/Sample.dll" target="lib"/>
  </files>
</package>

今回特にどこかで公開するようなパッケージではないのでlicenseUrlとかはデフォルトそのまま。

あとはnuget packしてあげればパッケージが作られます。

Kazuki:tools Yamamoto$ nuget pack Sample.nuspec -OutputDirectory ~/Nuget
'Sample.nuspec' からパッケージをビルドしています。
パッケージ '/Users/Yamamoto/Nuget/Sample.1.0.0.nupkg' が正常に作成されました。

私は~/Nugetに私物のNugetを置きXamarin Studioなどで参照させているので-OutputDirectoryオプションをつけています。

今回は特に問題なくパッケージが作成されました。

そして別プロジェクトでNugetパッケージを追加して早速使おうとするのですが・・・

f:id:crocus7724:20160910190400p:plain

表示されません。(本当なら名前空間にSampleがあるはず)

どういうことでしょうか?

とりあえずNugetパッケージはできているのに名前空間が出てこないということは.dllが参照されていない可能性があります。

相対パスがいけないのかということでsrcを絶対パスに。

<file src="/Users/Yamamoto/Projects/Sample/Sample/bin/Debug/Sample.dll" target="lib"/>

そしてまたnuget pack

Kazuki:tools Yamamoto$ nuget pack Sample.nuspec -OutputDirectory ~/Nuget
'Sample.nuspec' からパッケージをビルドしています。
Directory '/Users/Yamamoto/Projects/Sample/tools/Users/Yamamoto/Projects/Sample/Sample/bin/Debug' not found.

な に こ れ

なんと絶対パスで書いても相対パス扱いされてる。うーん意味がわからない。

なおNuspec Referenceのsrcの説明には

The location of the file or files to include. The path is relative to the NuSpec file unless an absolute path is specified.

とあります。私は英語苦手だけどGoogle先生の力を借りて読むと絶対パスを指定しない限り相対パスになるよとのこと。なら問題なく絶対パスが使えるはずなんだけど使えない辛い

なお解決方法は簡単です。諦めて.slnと同じ階層に.nuspecを置き

 <file src="Sample/bin/Debug/Sample.dll" target="lib"/>

にします。あとはnuget packしてあげてパッケージを新しいのにしてあげれば・・・

f:id:crocus7724:20160910190330p:plain

はい、無事に追加出来ました。

結論

MacでNugetパッケージを作るときは絶対パスも../も使わない

以上、Macでnuget怪奇現象でした。

Xamarin Studioでクリーンしてもクリーンされない問題

Xamarin Studioを使っているとたまに全てクリーンしてもクリーンされない時があります。 これのせいで不自然にファイルが残りそれが原因でバグることも・・・

この回避手段は簡単でobjフォルダとbinフォルダを消せば大丈夫です(少なくとも私が遭遇したケースでは)。

ただ手動で消すためだけにXamarin Studioから離れるのはアレですしそもそもXamarin Studioがちゃんと消してくれれば問題ありません。ということで...

Addin作りました

クリーンをしたとき、強制的にbinとobjを消す拡張機能を作りました。

使い方は簡単。Githubから.dllを落としてきて

/Applications/Xamarin Studio.app/Contents/Resources/lib/monodevelop/AddIns (macOSの場合)

に突っ込んでやります。あとは起動してソリューションを開き、適当にファイルを開いて上部のメニューバーのビルドを選べばこんな感じに出てきます。

f:id:crocus7724:20160908142956p:plain

Forced Cleanが現在選択しているプロジェクトのみでForced Clean Allがソリューション内の(Xamarin Studioが認識している)全プロジェクト向けになります。英語おかしいのは気にしないでください

内部処理はこんな感じ

using System.IO;
using MonoDevelop.Core;
using MonoDevelop.Core.Execution;
using MonoDevelop.Projects;

namespace ForcedCleanAddin
{
    public class ProjectCleanService
    {
        public static void Clean(Project project, OutputProgressMonitor monitor = null)
        {
            monitor?.Log.WriteLine($"{project.Name}をクリーン...");

            project.Clean(ProgressMonitorService.CleanProgressMonitor, project.DefaultConfiguration.Selector);

            var path = project.BaseDirectory.FullPath;

            var binPath = Path.Combine(path, "bin");

            if (Directory.Exists(binPath))
            {
                Directory.Delete(binPath, true);
                monitor?.Log.WriteLine($"{binPath}を削除しました!");
            }

            var objPath = Path.Combine(path, "obj");

            if (Directory.Exists(objPath))
            {
                Directory.Delete(objPath, true);
                monitor?.Log.WriteLine($"{objPath}を削除しました!");
            }

            monitor?.Log.WriteLine("完了");
            monitor?.Log.WriteLine("");
        }
    }
}


ようするに最初にXamarin Studio標準のクリーンをしたあと.csprojと同じ階層にbinobjがあったら強制的に消します。慈悲はありません。
もっと柔軟に(現在選択されているビルド設定(DebugとかReleaseとか)だけ消すとか)できますがこれが一番シンプルなのでこれのみです。シンプルイズベスト。

実際にこの問題にぶち当たる人がどれだけいるかわかりませんが(私は今のところめったにない)、参考までに。

追記

この拡張のVisual Studioバージョンをid:nuitsjpさんが作ってくださいました!!

www.nuits.jp

流石です! これでMac(Xamarin Studio)とWindows(Visual Studio)両方でこの問題に直面しても大丈夫ですね(なお私のプラグインがちゃんと動くとは言ってない)

Xamarin Studio Addinのつくりかた[Xamarin Studio]

Xamarin StudioではVisual Studioのように(?)、機能を拡張できます。しかしXamarin Studio Addinでググってもほとんど(というかほぼ)情報がありません。

せめてチュートリアルくらいはということで雑ですが書きました。 参考にしたのはXamarinのDevelopersページにあった"Extending Xamarin Studio with Add-Ins"です。

developer.xamarin.com

Edit->Insert Dateを押すと現在の日時が挿入される誰得機能です。

(最初はまじめに書こうと思ったのですが、ネタになりました)

環境

導入

導入はとても簡単です。 Xamarin Studioを立ち上げて上のツールバーのXamarin Studioをクリックしてメニューから"Add-ins..."を選択、Add-in ManagerからGalleryのAddin DevelopmentにあるAddin Makerをインストールすれば終了です。

f:id:crocus7724:20160613010312p:plain

とても簡単!!これならアドインの開発もとても簡単でしょう(((

ソリューション作成

最初にソリューション作成です。

いつもどおりファイル->新規->ソリューションを選択し、プロジェクト選択画面でその他のMiscellaneousを選ぶと真ん中いらへんにXamarin Studio Addinがあります。

f:id:crocus7724:20160613011030p:plain

これを選択し、ソリューション名を入れます。今回はサンプル通り'DateInserter'にしました。

Manifest.addin.xml編集

ソリューションが作成できたら、次にPropertiesフォルダの中にあるManifest.addin.xmlを編集します。 これで上のツールバーに表示される文字と押されたときに実行されるクラスを編集します。

<?xml version="1.0" encoding="UTF-8"?>
<ExtensionModel>
    <Extension
        path="/MonoDevelop/Ide/Commands/Edit">
        <Command
            id="DateInserter.DateInserterCommands.InsertDate"
            _label="Insert Date"
            defaultHandler="DateInserter.InsertDateHandler" />
    </Extension>
    <Extension
        path="/MonoDevelop/Ide/MainMenu/Edit">
        <CommandItem
            id="DateInserter.DateInserterCommands.InsertDate" />
    </Extension>
    <Runtime>
    </Runtime>
</ExtensionModel>

_labelが表示される文字列、defaultHandlerが押された時に実行されるクラス名ですかね?(自信ない

DateInserterCommands.cs

お次に新しい列挙型を追加します。
プロジェクトを右クリックし、追加->新しいファイルから空の列挙型を選択し、サンプル通り'DateInserterCommands'という名前にし、以下のように追加します。

namespace DateInserter
{
    public enum DateInserterCommands
    {
        InsertDate,
    }
}

これが上のManifest.addin.xmlのidと紐付いているのでしょう。

InsertDateHandler.cs

いよいよボタンが押されたときの処理です。クラスを追加し、以下のようにします。

using MonoDevelop.Components.Commands;
 using MonoDevelop.Ide;
 using MonoDevelop.Ide.Gui;   
 using Mono.TextEditor;
 using System;  

 namespace DateInserter
 {
     class InsertDateHandler : CommandHandler
     {
         protected override void Run ()
         {
              Document doc = IdeApp.Workbench.ActiveDocument;
              var textEditorData = doc.GetContent<ITextEditorDataProvider> ().GetTextEditorData ();  
              string date = DateTime.Now.ToString ();  
              textEditorData.InsertAtCaret (date); 
         }

         protected override void Update (CommandInfo info)
         {
               Document doc = IdeApp.Workbench.ActiveDocument;  
               info.Enabled = doc != null && doc.GetContent<ITextEditorDataProvider> () != null; 
         }   
     }
 }

Updateメソッドでボタンを押せるかどうかの処理を行い、Runメソッドで実際に押されたときの処理をしています。 これで完成です。それでは実行してみましょう!!

実行

f:id:crocus7724:20160613013642p:plain

_人人人人人人人人_
> ビルドエラー <
 ̄Y^Y^Y^Y^Y^Y^Y ̄

なんとMono.TextEditor名前空間が消失した模様。

サンプルが実行できない。終わった。先生の次回作にご期待ください。

がんばる

さすがにこれで終われないのでなんとかサンプルだけでも実行できるようにします。

幸いなことにXamarin StudioはIDEですから最終手段で片っ端から変数に値を入れてブレークポイントで実行時の中身をみて調べる事ができます。printfデバッグ!!printfデバッグ!!

とりあえずMono.TextEditorの代わりになるものを見つけなければいけません。 ということで適当にMonoDevelop名前空間を探していたら、MonoDevelop.Ide.Editor名前空間なるものを発見。その中にTextEditorというそれっぽいものがありました。

とりあえずこれのインスタンスの取得の仕方がわからなかったのでITextEditorDataProviderの代わりにTextEditorを突っ込んで実行してみました。

using MonoDevelop.Components.Commands;
using MonoDevelop.Ide;
using System;
using MonoDevelop.Ide.Editor;
using MonoDevelop.Ide.Gui;

namespace DateInserter
{
    class InsertDateHandler : CommandHandler
    {
        protected override void Run()
        {
            Document doc = IdeApp.Workbench.ActiveDocument;
            var textEditorData = doc.GetContent<TextEditor>();
            string date = DateTime.Now.ToString();
            textEditorData.InsertAtCaret(date);
        }

        protected override void Update(CommandInfo info)
        {
            Document doc = IdeApp.Workbench.ActiveDocument;
            info.Enabled = doc != null && doc.GetContent<TextEditor>() != null;
        }

    }
}

f:id:crocus7724:20160613015450p:plain

なんか実行できてる・・・

ひとまず適当にソリューションを開き、編集->InsertDateを押してみると・・・

f:id:crocus7724:20160613015648p:plain

無事に日付が挿入されました(((
(ほんとはgifにしたかったけど何故かはてなに弾かれてgifがアップロードできない・・・)

追記

普通にIdeApp.Workbench.ActiveDocument.Editorで'TextEditor'が取得できました。何故気づかなかったのか・・・

まとめ

Xamarin StudioのAdd-insは情報が少なく茨の道どころか道がない状態ですが、きっと誰かがそのうち便利なのを作ってくれるでしょう。

私には無理そうです。

参考

http://www.monodevelop.com/developers/articles/