xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

Unity Shortcut Cheat Sheet

Unity にはなんだかんだで便利系のマウス/キーボードショートカットがいくつも隠れています。よく忘れてしまうので備忘録としてメモ。

他にも面白いものが見つかったら追記するかもです。

移動 / 回転

ショートカット 説明
Shift + F ゲーム実行時、Scene ビューで選択中の GameObject を注視し続ける
Ctrl + Alt + F Hierarchy で選択した GameObject を現在の Scene の視点の中心に移動

生成

ショートカット 説明
Ctrl + Shift + F 空の GameObject を最上位階層に生成
Shift + Alt + F Hierarychy で選択している GameObject の子階層に空の GameObject を生成

Hierarchy 検索

方法 説明
t:<検索キーワード> 型でフィルタリング

その他

ショートカット 説明
Alt + 左クリック Hierarchy で選択している GameObject の階層をすべて展開/折り畳み
Shift + Alt + A Hierarchy で選択している GameObject の SetActive(bool) 切り替え
コンポーネントの Drag & Drop 別の GameObject にコンポーネントを移動

C# 7 をライブコーディングで解説してきました vol.3

10/22 (土)、日本マイクロソフト品川本社でCLR/H in Tokyo 第 11 回が開催され、そこに参加/登壇してきました。ハッシュタグは #clrhtky11

ハロウィンデーということで謎のハロウィン仮装という辱めを受けながら頑張りました!...「無難じゃね?」という声にめげない強い意志が必要と感じた次第です。ちなみに、勉強会に参加したハズだったのに ELLE (っぽい何か) の表紙モデルになったりと、催眠術だとか超スピードだとかそんなチャチなもんじゃあ断じてねぇような何かが起こったりしました。

イベントが終わってみれば、ハイライトは JK の父兄参観の写真で決まりだった思うので、僕の仮装なんてハナからあってもなくても良かったww

セッション資料

セッション資料は前回/前々回同様、ライブコーディングによるデモのおさらいという形で簡単にまとめてあります。PDF 形式で Docs.com に置いてあるので、ご自由にダウンロードなどしてください。今回はハロウィンデーということで、タイトルスライドのデザインもそれに合わせたものにしてみました。という謎の配慮。

おさらい形式のスライドなので、より詳しく知りたい方は以下の記事やサンプルコードを参照ください。

ちょっとしたトラブル

当日の朝に Roslyn の最新のコミットを pull してビルドしたものでデモしました。どーせやるなら正真正銘世界最新の C# コンパイラでやらなきゃですよね!とは言えそんな危ない橋を渡ろうとするので、セッション開始 20 分前に Visual Studio が立ち上がらなくなったりして冷や汗をかいたり...。

さらにセッション中に不慮の事故でプロジェクターが壊れて数分間中断して、なんだかんだ結構焦って「何もしてないのに壊れた」などとエンジニアが言ってはいけない言葉 (謎) を平気で言ってしまったり...w

本番には魔物が住んでいるというのを痛感した次第ですが、焦らずトラブル対応できるようになりたいですね!

C# 7 のサンプルコードを GitHub に公開しました

これまで C# 7 の新機能を検証するときは何度もサンプルコードを作って / 消してを繰り返していたのですが...、ぎたぱそ先生 (@guitarrapc_tech) に「作ってくれたら僕が喜ぶ!」と言われたのもあって、約束を守るために一念発起してまとました!

GitHub で公開

以下の GitHub リポジトリで公開しています。Visual Studio 15 RC の準備も始まっている昨今、C# 7 への機能追加はそうそうないのではないかと思いますが、今後ももし機能が増えたりしたら随時追加していきます。

こんなのも追加してほしいなどのリクエストがあれば、Pull-Request なり送っていただけると喜びます!

ラムダ形式メンバーの拡張

2016/09/24 (土) の朝、Expression-Bodied Everything (= どこでもラムダっぽく) に関する Pull-Request が Roslyn の master ブランチにマージされました!

C# 6 ではプロパティとメソッドの 2 箇所についてラムダ形式のメンバーを記述することができましたが、それをさらに拡張していろんなところで使えるようにするというものです。

C# 6 の頃から「ラムダ形式」と書いているのでその名残でタイトルを付けましたが、「式形式 / 式っぽい / 式の体をした」と言うのかもしれません。ここら辺は日本語訳が正式には決まってないと思うので結構テキトーです...。

f:id:xin9le:20160927231108p:plain

新たに使えるようになる場所

C# 7 から増えたもの (= 2016/09/27 時点で動作するもの) は以下の 4 つです。

  • コンストラクタ
  • デストラクタ
  • プロパティのアクセサ
  • インデクサ

例えば以下のように書けます。

class Program
{
    //--- プロパティのアクセサ
    //--- get だけとか set だけもできる
    public string Property
    {
        get => "Getter Property";
        set => Console.WriteLine(value);
    }

    //--- インデクサも同様に
    public string this[int index]
    {
        get => $"{index} : Getter Indexer";
        set => Console.WriteLine($"{index} : {value}");
    }

    //--- コンストラクタ / デストラクタもこの通り
    public Program(string text) => Console.WriteLine(text);
    ~Program() => Console.WriteLine("Destructor");

    static void Main()
    {
        var x = new Program("Constructor");
        x.Property = "Setter Property";
        Console.WriteLine(x.Property);
        x[123] = "Setter Indexer";
        Console.WriteLine(x[123]);
    }
}

/*
Constructor
Setter Property
Getter Property
123 : Setter Indexer
123 : Getter Indexer
Destructor
*/

逆コンパイル

先の例を逆コンパイルしてみると以下のようになります。お分かりの通り、これまでの書き方をより簡易にするための糖衣構文でしかありません。

class Program
{
    public string Property
    {
        get { return "Getter Property"; }
        set { Console.WriteLine(value); }
    }

    public string this[int index]
    {
        get { return string.Format("{0} : Getter Indexer", index); }
        set { Console.WriteLine(string.Format("{0} : {1}", index, value)); }
    }

    public Program(string text)
    {
        Console.WriteLine(text);
    }

    ~Program()
    {
        Console.WriteLine("Destructor");
    }

    private static void Main()
    {
        Program x = new Program("Constructor");
        x.Property = "Setter Property";
        Console.WriteLine(x.Property);
        x[123] = "Setter Indexer";
        Console.WriteLine(x[123]);
    }
}

[おまけ] イベントへの対応

プロパティの get/set ができるのなら、(滅多に使われない) イベントの add/remove もできるのでは?と思った方もいらっしゃるかもしれません。ただ、残念ながら執筆時点 (2016/09/27) では csc.exe がクラッシュして死にます。Roslyn の実装を見ると add/remove にも対応してそうな雰囲気を感じるところもあるのですが...。

private EventHandler click;
public event EventHandler Click
{
    //--- 残念ながらコンパイラがクラッシュする
    add => this.click += value;
    remove => this.click -= value;
}

イベントビューアで確認するとスタックトレースはこんな感じで吐かれていました。

アプリケーション:csc.exe
フレームワークのバージョン:v4.0.30319
説明: ハンドルされない例外のため、プロセスが中止されました。
例外情報:System.InvalidOperationException
   場所 Microsoft.Cci.ReferenceIndexer.ProcessMethodBody(Microsoft.Cci.IMethodDefinition)
   場所 Microsoft.Cci.MetadataVisitor.Visit(Microsoft.Cci.ITypeDefinitionMember)
   場所 Microsoft.Cci.MetadataVisitor.Visit(System.Collections.Generic.IEnumerable`1<Microsoft.Cci.IMethodDefinition>)
   場所 Microsoft.Cci.ReferenceIndexerBase.Visit(Microsoft.Cci.ITypeDefinition)
   場所 Microsoft.Cci.MetadataVisitor.Visit(System.Collections.Generic.IEnumerable`1<Microsoft.Cci.INamedTypeDefinition>)
   場所 Microsoft.Cci.ReferenceIndexer.Visit(Microsoft.CodeAnalysis.Emit.CommonPEModuleBuilder)
   場所 Microsoft.Cci.MetadataWriter.CreateIndices()
   場所 Microsoft.Cci.MetadataWriter.BuildMetadataAndIL(Microsoft.Cci.PdbWriter, System.Reflection.Metadata.BlobBuilder, System.Reflection.Metadata.BlobBuilder, System.Reflection.Metadata.BlobBuilder, System.Reflection.Metadata.Blob ByRef, System.Reflection.Metadata.Blob ByRef)
   場所 Microsoft.Cci.PeWriter.WritePeToStream(Microsoft.CodeAnalysis.Emit.EmitContext, Microsoft.CodeAnalysis.CommonMessageProvider, System.Func`1<System.IO.Stream>, System.Func`1<System.IO.Stream>, Microsoft.Cci.PdbWriter, System.String, Boolean, Boolean, System.Threading.CancellationToken)
   場所 Microsoft.CodeAnalysis.Compilation.SerializeToPeStream(Microsoft.CodeAnalysis.Emit.CommonPEModuleBuilder, EmitStreamProvider, EmitStreamProvider, System.Func`1<System.Object>, Microsoft.CodeAnalysis.DiagnosticBag, Boolean, System.Threading.CancellationToken)
   場所 Microsoft.CodeAnalysis.CommonCompiler.RunCore(System.IO.TextWriter, Microsoft.CodeAnalysis.ErrorLogger, System.Threading.CancellationToken)
   場所 Microsoft.CodeAnalysis.CommonCompiler.Run(System.IO.TextWriter, System.Threading.CancellationToken)
   場所 Microsoft.CodeAnalysis.CSharp.CommandLine.Csc+<>c__DisplayClass1_0.<Run>b__0(System.IO.TextWriter)
   場所 Microsoft.CodeAnalysis.CommandLine.ConsoleUtil.RunWithUtf8Output[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]](System.Func`2<System.IO.TextWriter,Int32>)
   場所 Microsoft.CodeAnalysis.CSharp.CommandLine.Csc.Run(System.String[], Microsoft.CodeAnalysis.CommandLine.BuildPaths, System.IO.TextWriter, Microsoft.CodeAnalysis.IAnalyzerAssemblyLoader)
   場所 Microsoft.CodeAnalysis.CommandLine.DesktopBuildClient.RunLocalCompilation(System.String[], Microsoft.CodeAnalysis.CommandLine.BuildPaths, System.IO.TextWriter)
   場所 Microsoft.CodeAnalysis.CommandLine.BuildClient.RunCompilation(System.Collections.Generic.IEnumerable`1<System.String>, Microsoft.CodeAnalysis.CommandLine.BuildPaths, System.IO.TextWriter)
   場所 Microsoft.CodeAnalysis.CommandLine.DesktopBuildClient.Run(System.Collections.Generic.IEnumerable`1<System.String>, System.Collections.Generic.IEnumerable`1<System.String>, Microsoft.CodeAnalysis.CommandLine.RequestLanguage, Microsoft.CodeAnalysis.CommandLine.CompileFunc, Microsoft.CodeAnalysis.IAnalyzerAssemblyLoader)
   場所 Microsoft.CodeAnalysis.CSharp.CommandLine.Program.Main(System.String[], System.String[])
   場所 Microsoft.CodeAnalysis.CSharp.CommandLine.Program.Main(System.String[])

Expression-Bodied なイベントへの対応も提案としては出ているので、今後 (どういった書き味になるかはさておき) 対応が入るかもしれませんね。

throw 式

2016/09/21 (水) の朝方、throw 式に関する Pull-Request が master にマージされました!これにより、これまでステートメント (文) としてのみ提供されていた throw を式として記述できるようになります。

f:id:xin9le:20160924225231p:plain

throw 式が使えるところ

パッと確認した範囲では、以下の箇所で使えました。

  • 条件演算子 (三項演算子)
  • null 結合演算子
  • ラムダ形式のメンバー

条件演算子 (三項演算子)

条件演算子で throw 式を使ってみると以下のような感じになります。

//--- C# 7 ではこう書ける
static int IntParse(string value)
    => int.TryParse(value, out var result)
    ?  result
    :  throw new ArgumentException(nameof(value));

//--- C# 6 までの書き方
static int IntParse(string value)
{
    int result;
    if (int.TryParse(value, out result))
        return result;

    throw new ArgumentException(nameof(value));
}

C# 7 のコードを逆コンパイルしてみると以下のようになります。C# 6 までのコードと同様の展開がされることが分かりますね。

//--- 逆コンパイル結果
private static int IntParse(string value)
{
    int result;
    if (!int.TryParse(value, out result))
    {
        throw new ArgumentException("value");
    }
    return result;
}

null 結合演算子

null 結合演算子に対しても同じように使うことができます。

//--- C# 7
static object Assert(object x)
    => x ?? throw new ArgumentNullException(nameof(x));

//--- 逆コンパイル結果
private static object Assert(object x)
{
    if (x == null)
    {
        throw new ArgumentNullException("x");
    }
    return x;
}

ラムダ形式のメンバー

C# 6 で導入された => によるプロパティやメソッドの簡易記法 (= ラムダ形式メンバー) でも使えます。例えば下記のように、未実装時の記述がシンプルで楽になりますね。

//--- C# 7
public string Name => throw new NotImplementedException();
public string SayHello() => throw new NotImplementedException();

//--- 逆コンパイル結果
public string Name{ get { throw new NotImplementedException(); }}
public string SayHello(){ throw new NotImplementedException(); }

[おまけ] throw null;

みなさん、nullthrow するとどうなるかご存知ですか?実は NullReferenceException が飛びます。

try
{
    //--- 以下のふたつは等価
    throw null;
    throw new NullReferenceException();
}
catch
{}

違う型の例外でも NullRefereneceException になります。

try
{
    NotSupportedException ex = null;
    string a = null;
    var b = a ?? throw ex;  //--- NotSupportedException は飛ばない
}
catch (Exception ex2)
{
    //--- System.NullReferenceException
    Console.WriteLine(ex2.GetType());
}

throw 式の結合優先度

岩永先生 (@ufcpp) が throw 式の結合優先度を調べて教えてくださいました。まとめてくださった Gist をそのまま拝借するとこんな感じらしいです。

最初どうしてこうなるのか全然わからなかった以下のような例も、結合優先度のおかげで理屈が分かって納得しました。

try
{
    //--- throw null されると思ってたのに、されないで ArgumentNullException が飛んだ
    NotSupportedException ex = null;
    string a = null;
    var b = a ?? throw ex ?? throw new ArgumentNullException();

    //--- けど ↑↑ は結合優先度の問題なので、実はこれと一緒
  //var b = a ?? throw (ex ?? throw new ArgumentNullException());
}
catch (Exception ex2)
{
    //--- System.ArgumentNullException
    Console.WriteLine(ex2.GetType());
}

上記を逆コンパイルすると以下のように展開されます。

try
{
    NotSupportedException ex = null;
    string a = null;
    if (a == null)
    {
        NotSupportedException expr_0A = ex;
        if (expr_0A == null)
        {
            throw new ArgumentNullException();
        }
        throw expr_0A;
    }
    var b = a;
}
catch (Exception ex2)
{
    Console.WriteLine(ex2.GetType());
}

throw 式の型

例えば条件演算子の場合、コンパイラはその戻り値の型を決定しなければなりません。これまでは以下のようになっていました。

//--- こんな継承関係があるとする
class Base {}
class A : Base {}
class B : Base {}

//--- A は Base になり得るので x は Base 型
var x = true ? new A() : new Base();

//--- コンパイルエラー : 継承元を探してくれたりはしない
var x = true ? new A() : new B();

throw 式は条件演算子に書くことができると先に書きました。この場合 throw 式の型はどうなるのかというと、答えは「どんな型にでもなれる」です。こんな型を「bottom 型 (= すべての型から派生している型)」と言います。.NET Framework において双対するのが object 型で、これを「top 型 (= すべての型の基底となる型)」と言うそうです。

//--- x の型は A と推論される
var x = true ? new A() : throw new Exception();

//--- x の型は B と推論される
var x = true ? throw new Exception() : new B();

つまり条件演算子は throw 式 ではない方の型で推論される動きになるのですが、両方を throw 式にしたらどうなるか。これは (当然) コンパイルエラーになります。

//--- 型推論できないのでエラー
//--- Type of conditional expression cannot be determined because there is no implicit conversion between '<throw>' and '<throw>'
var x = true ? throw new NotSupportedException() : new ArgumentNullException();