ラベル Visual Studio の投稿を表示しています。 すべての投稿を表示
ラベル Visual Studio の投稿を表示しています。 すべての投稿を表示

2022年3月19日土曜日

Visual Studio 2022からSSHでgit pushする

備忘録程度に。

以前Synology NAS上に立てたGitサーバーにVS2022からSSH経由でpushしようとしたところ、以下のようなエラーが出て失敗しました。

master をプッシュしています
ssh_askpass: exec(c:/program files/microsoft visual studio/2022/community/common7/ide/commonextensions/microsoft/teamfoundation/team explorer/Git/mingw32/libexec/git-core\\git-askpass.exe): No such file or directory
Permission denied, please try again.
ssh_askpass: exec(c:/program files/microsoft visual studio/2022/community/common7/ide/commonextensions/microsoft/teamfoundation/team explorer/Git/mingw32/libexec/git-core\\git-askpass.exe): No such file or directory
Permission denied, please try again.
ssh_askpass: exec(c:/program files/microsoft visual studio/2022/community/common7/ide/commonextensions/microsoft/teamfoundation/team explorer/Git/mingw32/libexec/git-core\\git-askpass.exe): No such file or directory
gituser@192.168.*.*: Permission denied (publickey,password).
リモート リポジトリにブランチをプッシュできませんでした。詳しくは、出力ウィンドウをご覧ください。
リモート リポジトリへのブランチのプッシュ中にエラーが発生しました: Git failed with a fatal error.
Git failed with a fatal error.
Could not read from remote repository.

git-askpass.exeというバイナリが無いのが原因のようです。このバイナリは以下のサイトから入手できます。 

https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases/tag/v1.16.3

ここからダウンロードしたファイルを解凍し、そのまま当該フォルダに入れてやればOKです。

ちなみに最新のSynologyのOSではSSHポートが2222に変更されているようです(設定から変えられます)。ここもハマりポイントですので注意してください。

2017年4月22日土曜日

Visual Studio 2017でXamarin.Forms入門

「申し訳ないがJavaはNG」

この言葉を座右の銘に置く私としては、常にAndroidアプリ開発は戦いです。
AndroidはJava開発が最も基本で、C#で開発するにはXamarinなどの環境が必要です。

Xamarinは昔は有料だったうえ、ドキュメンテーションも少なく非常にとっつきにくいものでした。しかし、Visual Studio 2015にはオプションでXamarinをインストールできるようになり、ついに2016年にはMicrosoftに買収され、Visual Studio 2017ではモバイル開発の中核に置かれるようになりました。

というわけで、私もXamarin初心者ですが、VS2017もインストールしたことだし本格的にいじってみようと思います。

Visual Studio 2017のインストール時にXamarin関係のオプションを選択してインストールすると、プロジェクトテンプレートにXamarin.Formsが現れます。
そうするとテンプレートの選択が出てきます。
空のアプリを選択してOKを押します。
そして、プロジェクトをビルドして実行すると…
見事にエミュレーター上でAndroidアプリが動きました。

しかし、このプロジェクトテンプレートには問題が1つあります。
上の画像にも書きましたが、最初のページに相当する「MainPage.xaml」がどのプロジェクトにも含まれていません。
そのため、Visual Studioがコードの依存関係等を理解できずに、IntelliSenseがほとんど働きません。編集しているとコードのいたるところに参照が解決できずに波線が引かれます。とても鬱陶しいです。

この解決には苦労しましたが、とりあえず次のようにやるとできるようです。

まずは、ソリューションにXamarin.Forms用のPCL(Portable Class Library)を追加します。
次は各プラットフォームのプロジェクトでこのプロジェクトへの参照を追加します。
さっき追加したプロジェクトに適当なMainPage.xamlを作ります。
このテンプレートには、XAMLだけでなくコードビハインドにも余計なコードが書いてあるので注意してください。コードビハインドで架空のViewModelクラス(ContentPageViewModel)をインスタンス化しようとしてくるのでコンパイルエラーが出ます。
public partial class App : Application
{
    public App ()
    {
        InitializeComponent();

        //MainPage = new XamarinFormsTest.MainPage();
        MainPage = new XamarinFormsTest.PCL.Views.MainPage();
    }

    protected override void OnStart ()
    {
        // Handle when your app starts
    }

    protected override void OnSleep ()
    {
        // Handle when your app sleeps
    }

    protected override void OnResume ()
    {
        // Handle when your app resumes
    }
}
最後はApp.xamlのコードビハインドでMainPageのインスタンスを自分が追加してやったPCLのほうへ書き換えてやればおkです。もとあったMainPage.xamlはいらないので削除してしまってもよいでしょう。
これでMainPageをPCL上に移動することに成功しました。

PCL上にUIを置けるということは、Nugetなどを活用して適当なライブラリを入れることもできるということですので、例えばXamarin.Formsに対応しているMVVMライブラリ「Prism」を入れてみましょう。
そうすれば、ほとんどWPFと変わらずにXAML+C#でAndroidアプリを開発することができます。
このアプリでは、スライダーを 動かすとそれに追従して上に表示されている数字が変わります。「ADJUST TO 50」ボタンを押すと数字とスライダーが50の位置に移動します。
XAML+C#のデータバインディングのデモをするにはこれくらいやれば十分でしょう。

例えばコントロールの配置が「HorizontalOptions」「VerticalOptions」とかいう名前のプロパティだったりと、若干WPFと違うところもありますが、まあこれくらいならフィーリングでわかるレベルでしょう。
あとはXAMLデザイナーでリアルタイムに配置の絵が表示される機能は内容ですが、この程度ならば個人的にはまあギリギリ許容できます。

これくらいWPFの開発にそっくりだと、普段からWPFで開発している身としてはとてもありがたいですね。

というわけで、いろいろといじってみようと思います。はい。

2017年4月18日火曜日

最強のWPF MVVMインフラ「Livet」をVS2017で使う

世の中的にはWindows開発と言えばUWPとかなのかもしれませんが、まだまだレガシーなWindowsデスクトップアプリが滅びる様子はありませんよね。
そのレガシーな中でも最も近代的なプラットフォームと言えばやはりWPFで、WPFでアプリケーションを作るのならばやはりLivetは欠かせません。

Livet - ProjectHome

しかし、LivetはVS2015対応としてリリースされたver.1.3を最後に更新されておりません。
作者自身もVS2015対応版がLivet 1.xの最終リリースとする旨の発言をしており、今後はLivet2を開発すると言っています。

ugaya40/Livet2 - GitHub

しかし、現時点でLivet2のLastest commitは2015年8月。Livetの最大の武器ともいえるプロジェクトテンプレート付きのインストーラはおろか、正式リリースさえされていない状態です。このような状況ではVS2017対応は絶望的です。

そうすると、何とかして自力でLivetをVS2017にインストールしなければなりません。

さて、VS2013とVS2015がリリースされた直後も、自力でLivetのプロジェクトテンプレートを組み込んでいた漢たちはいました。

最強のWPF MVVMインフラ「Livet」をVS2013で使う
最強のWPF MVVMインフラ「Livet」をVS2015で使う

Livet自体もこの時からほとんど進んでいませんし、こちらのページで紹介されているバッチファイルをちょっと改造すればいけるでしょう。

の前に、コードスニペットを改良しておきます。
本来、その改良はVS2015対応でやるべきでした。

VS2015と同時にリリースされたC#6でnameof演算子が導入されています。この、INotifyPropertyChangedインターフェースの実装に使ってくださいと言わんばかりの機能を通知プロパティのスニペットに使わない理由は無いでしょう。
ちなみに、.NET4.5からはCallerMemberName属性を使うことで自動的に呼び出し元のプロパティ名を引数としてメソッドに引き渡す機能があるので、通知プロパティの実装にそれを使う人も多いようです(Livetももちろんそれに対応しています)。しかし、「書いてもいないものが勝手に生成される」という気持ち悪さから私はこの言語機能を使いたくないので、nameofを使ったコードを書くことにしています。

というわけで、GitHubからダウンロードしたLivet-master\Installer\Files\Snippets内のLivetProperty_NET45_CSharp.snippetは削除して、LivetProperty_NET40_CSharp.snippetをnameof演算子に書き換えておきましょう(VBも必要があればやっておくといいと思います)。
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2008/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Livet プロパティ</Title>
      <Shortcut>lprop</Shortcut>
      <Author>Livet Project</Author>
      <Description>プロパティを作成します</Description>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Imports>
        <Import>
          <Namespace>Livet</Namespace>
        </Import>
      </Imports>
      <Declarations>
        <Literal>
          <ID>type</ID>
          <ToolTip>プロパティの型</ToolTip>
          <Default>string</Default>
        </Literal>
        <Literal>
          <ID>name</ID>
          <ToolTip>プロパティ名</ToolTip>
          <Default>MyProperty</Default>
        </Literal>
      </Declarations>
      <Code Language="csharp">
<![CDATA[
#region $name$変更通知プロパティ
private $type$ _$name$;

public $type$ $name$
{
get
{ return _$name$; }
set
{ $end$
if(_$name$ == value)
return; 
_$name$ = value; 
RaisePropertyChanged(nameof($name$));
}
}
#endregion
]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>
最後に、上記のVS2015版のサイトのbatファイルの「2015」を「2017」に置換してから実行してやればOKです。
@echo off
cd %~d0%~p0

@echo on
echo copy snippets
xcopy /I /Y .\Files\Snippets\*_CSharp.* "%UserProfile%\Documents\Visual Studio 2017\Code Snippets\Visual C#\My Code Snippets\Livet"
xcopy /I /Y .\Files\Snippets\*_VB.* "%UserProfile%\Documents\Visual Studio 2017\Code Snippets\Visual Basic\My Code Snippets\Livet"

echo project templates
call :copy_project_template_csharp Livet_WPF4.0_CSharp
call :copy_project_template_csharp Livet_WPF4.5_CSharp

call :copy_project_template_vb Livet_WPF4.0_VB
call :copy_project_template_vb Livet_WPF4.5_VB

echo item templates
call :copy_item_template_csharp LivetInteractionMessageAction_CSharp
call :copy_item_template_csharp LivetMessage_CSharp
call :copy_item_template_csharp LivetModel_CSharp
call :copy_item_template_csharp LivetViewModel_CSharp
call :copy_item_template_csharp LivetWindow_CSharp

call :copy_item_template_vb LivetInteractionMessageAction_VB
call :copy_item_template_vb LivetMessage_VB
call :copy_item_template_vb LivetModel_VB
call :copy_item_template_vb LivetViewModel_VB
call :copy_item_template_vb LivetWindow_VB

echo simple install completed!!!
pause

:copy_project_template_csharp
xcopy /I /E /Y .\Files\Templates\%1 "%UserProfile%\Documents\Visual Studio 2017\Templates\ProjectTemplates\Visual C#\%1"
exit /b

:copy_project_template_vb
xcopy /I /E /Y .\Files\Templates\%1 "%UserProfile%\Documents\Visual Studio 2017\Templates\ProjectTemplates\Visual Basic\%1"
exit /b

:copy_item_template_csharp
xcopy /I /E /Y .\Files\Templates\%1 "%UserProfile%\Documents\Visual Studio 2017\Templates\ItemTemplates\Visual C#\%1"
exit /b

:copy_item_template_vb
xcopy /I /E /Y .\Files\Templates\%1 "%UserProfile%\Documents\Visual Studio 2017\Templates\ItemTemplates\Visual Basic\%1"
exit /b
これで見事VS2017に追加されました。めでたしめでたし。


2014年10月15日水曜日

非同期処理とAggregateException

以前の記事で2048のGUIを作ろうとかオートソルバーを作ろうとか言っておきながら手が付いていないのですが、その件は一旦保留してまた別の記事を書いてみようと思います。

さて、以前、LINQ to Twitter v3の記事を書きましたが、ちょっとこれについてまたいじっていたら例外処理で躓きましたので、一般化して話をしていこうと思います。
例えばネットにつながっていないPCからLINQ to Twitterでタイムラインを読み込もうとしてしまうようなことがあるかもしれません。そういった場合、例外が吐かれます。 まあWebExceptionとかHttpRequestExceptionとかそのあたりが吐かれるんだろうなーと思って適当に例外処理していると痛い目に遭いました。AggregateExceptionとかいう見慣れない例外が吐かれます。

さて、AggregateExceptionが一体どのような例外なのかを調べるためにMSDNに行ってみます。

AggregateException クラス (System)

「アプリケーションの実行中に発生する1つ以上のエラーを表します。」って書いてありますがその本質はよくわかりません。どうもInnerExceptionsというコレクションがあり、複数の例外をこのクラスで保持することができるようですが、なぜそのような例外が吐かれるのかがわかりません。素直にWebExceptionとかじゃダメなの?複数の場所で例外が発生するような状況だったとしても、1つ目に遭遇した例外が吐かれればそれで問題ないよね?というか、通常の例外処理がそうでしょ?なのになぜ敢えてこのように複数の例外をまとめるような例外が吐かれるのでしょう。


その疑問に答えるべく、まずひとつこの例外を吐くメソッドを挙げてみます。
わかりやすい例としてParallel.Forが挙げられます。 指定した範囲の整数を受け取るデリゲートを同時並行で動かすメソッドです。もっと正確にいえば、同時並行に動かす可能性があるです。詳しい説明は省きますが、スレッドプールを利用して実行するため、環境や設定によっては単一スレッドでの実行になるかもしれませんし、その順序も、どの値同士が並列で実行されるかもわかりません。こんな運要素の大きなメソッドですが、この中で特定の値で例外を吐くようなプログラムを書いて実行してみます。

try {
    Parallel.For(0, 100, (i) => {
        if(i == 0)
            throw new InvalidOperationException();
        if(i == 1)
            throw new ArgumentException();
        Console.WriteLine(i);
    });
}
catch(AggregateException e) {
    ShowAggregateException(e);
}

ちなみにShowAggregateExceptionメソッドは次のように実装しています。

static void ShowAggregateException(AggregateException ae)
{
    ShowAggregateException(ae, 0);
}

static void ShowAggregateException(AggregateException ae, int depth)
{
    Console.WriteLine(new string(' ', depth) + ae.GetType().Name);
    foreach(var e in ae.InnerExceptions) {
        if(e is AggregateException)
            ShowAggregateException((AggregateException)e, depth + 1);
        else
            Console.WriteLine(new string(' ', depth + 1) + e.GetType().Name);
    }
}

いわゆる再帰ですね。AggregateExceptionに含まれるすべての内部例外を表示しますが、その時にその内部例外にAggregateExceptionが含まれていたら再帰的にこいつもこのメソッドで表示させています。そして、階層が深まるたびにインデントを追加しています。new string(' ', depth + 1)がそれにあたります。

さて、これを実行するわけですが、VisualStudioを使う場合は「デバッグなしで開始」で実行する必要があります。「デバッグ開始」でやると、下の図のようにthrow文のところで「ハンドルされていない例外としてVisualStudioにキャッチされてしまい、AggregateExceptionが活躍しているところが見られません。


同じコードでデバッグ時の挙動とデバッグしないときの挙動で違うのも困りものですが、まあ、AggregateException行きになる時点でハンドルされていない例外とも言えるのか…な?なんていうか、単にVisual Studioの対応力不足な気もしますが。
さて、話を戻します。実際にデバッグなしで実行してみると、例えば下図のような実行結果が得られます。


表示されている数字が昇順や降順でないあたり、並列処理が実行されていそうな雰囲気ですね。
そして、InvalidOperationExceptionとArgumentExceptionが同時に吐かれていることがわかると思います。なるほど、確かにこういう場面を想定するとAggregateExceptionも役に立ちそうですね。
ちなみにですが、先ほども言いましたがこのParallel.Forはかなり気まぐれなメソッドです。何回か実行してみると、表示される数字の順番は毎回異なりますし、InvalidOperationExceptionしか例外が吐かれないこともあります。おそらくどこか1つでも例外を吐いたら直ちに新しい処理を開始するのをやめてメインルーチンに処理を返す仕様になっているんでしょうね。たまたま0と1が別スレッドで同時に実行された時のみ、上記のコードではこのような実行結果を吐くでしょう。なので、もしかしたらシングルコアのCPUのマシンとかだったらこれは一生再現できないかもしれませんね。


さてさて、ここではParallel.Forを使ってみましたが、もともとはこれは非同期処理全般に使われている例外のようです。非同期処理の基礎で出てくるこのあたりのメソッドやプロパティでもAggregateExceptionが吐かれるようです。

Task.Wait メソッド  (System.Threading.Tasks)
Task(TResult).Result プロパティ  (System.Threading.Tasks)

Task.Resultのほうには明言されていませんが、これはタスクの終了を待ってから制御を返すプロパティなので、内部的にWaitを呼んでいるのか知りませんが、これもAggregateExceptionを吐くことがあるようです。

Task t = Task.Factory.StartNew(() => {
    throw new InvalidOperationException();
});
Thread.Sleep(1000);
try {
    t.Wait();
}
catch(AggregateException e) {
    ShowAggregateException(e);
}

さて、このように非同期処理中で例外を吐くプログラムを書いてみましょう。
タスクを開始してからメインスレッドでは怪しげな1秒のスリープを入れてみました。こうすることで、t.Wait()が呼ばれる前に例外を吐くスレッドのほうではいかにも例外を吐いたであろう状態にあることは明らかです(もちろん、スレッドプールが2スレッド以上あることが前提ですが)。ですが、その例外は即座にプログラムの実行に影響を及ぼしません。t.Wait()が呼び出されたときに例外が吐かれます。予想通り、AggregateExceptionの中にはInvalidOperationExceptionが1つ入っています。

では、この別スレッドの中でさらに別スレッドにを実行し、そこで例外を吐かせてみたらどうでしょう。

Task t = Task.Factory.StartNew(() => {
    Task.Factory.StartNew(() => {
        throw new ArgumentException();
    }, TaskCreationOptions.AttachedToParent);
    throw new InvalidOperationException();
});
try {
    t.Wait();
}
catch(AggregateException e) {
    ShowAggregateException(e);
}

ここで、子スレッドにはTaskCreationOptions.AttachedToParentオプションを付けていることに気を付けてください。これを付けないと、ArgumentExceptionを吐くスレッドが終わる前にInvalidOperationExceptionを吐くスレッドが終わってしまって、ArgumentExceptionのほうがAggregateExceptionに含まれなくなってしまいます。


この通り、見事にInvalidOperationExceptionとAggregateExceptionを含むAggregateExceptionが吐かれました。


さて、ここまで見てきたら、何となくなぜ非同期処理や並列処理でAggregateExceptionが必要なのかがわかってきたかと思います。非同期処理や並列処理は根本的に複数の例外を同時に吐く可能性をはらんでいる点があるからAggregateExceptionが必要というわけです。
しかし、よくよく考えれば、例えば別スレッド内での例外をそのままWait()呼び出し時に再スローするような仕組みをつくった場合、Waitメソッドが吐く例外の種類をメソッドのドキュメンテーションに定義できなくなりますし、Waitメソッドそのものが吐いた例外なのか、それとも別スレッド内のユーザーが書いた処理に起因する例外なのかを判別することもできません。そういう意味からも、例外をカプセル化してしまったほうが良い、といった判断なのかもしれません。

最後に、少し実用的なコードを書いてみようと思います。

using(WebClient wc = new WebClient()) {
    var t = wc.DownloadStringTaskAsync(@"http://www.google.co.jp");
    try {
        Console.WriteLine(t.Result);
    }
    catch(AggregateException ae) {
        ae.Handle((e)=>{
            if(e is WebException) {
                Console.WriteLine(e.GetType().Name);
                Console.WriteLine(" " + e.Message);
                return true;
            } else
                return false;
        });
    }
}

こんな感じでどうでしょうか。
非同期にGoogleのトップページのソースをダウンロードしています。正常にダウンロードできれば画面にそのテキストが表示されます。一方、正常にダウンロードできなかったらAggregateExceptionが発生します。このHandleメソッドでは、InnerExceptionsに含まれてるすべての例外についての例外処理のデリゲートを 書いています。処理した例外はtrueを返し、処理しなかったらelseを返します。
例えばデスクトップPCならLANケーブルを引っこ抜いて、ノートPCなら機内モードにするなどで正常にネットにつながらない状態にしてみましょう。そうすると、見事にAggregateExceptionが吐かれ、そのWebExceptionについての例外情報が画面に表示されます。

LINQ to Twitter v3でもクエリが並列非同期処理となっているためか例外はAggregateExceptionに内包される形で吐かれるようです。なので、上記のようにAggregateExceptionを処理する形に書き直しておけば、各々の例外に対応することができるようになります。


今回は非同期処理の例外処理について紹介しました。
非同期処理や並列処理の簡単さはC#5.0/.NET4.5あたりの目玉機能ですが、この辺りを使うときはぜひとも気を使ってみたいところですね。