30
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TryGetComponentとout、out var のお話

Last updated at Posted at 2019-11-29

##Unity 2019.2 から
TryGetComponent という GetComponent 相当のメソッドが追加されています。
このメソッドの紹介をしたいのですが、そのためには切っても切れない、 out var(out変数宣言) についてから書きたいと思います。
「out var ぐらい知ってらぁ!!」という方は、 TryGetComponentからどうぞ。
「どっちも知ってらぁ!!」という方は・・・。お疲れ様でございました。

out変数宣言

out 宣言。 使ってます? 多分、Unityでゲーム作っている上で自作のなんらかでこのキーワードをわざわざ使ってメソッド宣言している人はほとんどいないでしょう(偏見)

そもそもの機能としては、「out を付けた引数で指定した変数はメソッド内で必ず結果が入りますよ。」 という宣言です。

outを使ったメソッド
        private void PlusMinusOne(int origin, out int plusResult, out int minusResult)
        {
            plusResult = origin + 1;
            minusResult = origin - 1; //out キーワードを付けた引数への代入が存在しない場合はエラー
        }
呼び出し側
        public void Start()
        {
            int plusResult
            int minusResult;
            PlusMinusOne(100, out plusResult, out minusResult); //呼び出す方も out キーワードを付ける必要がある
            Debug.Log($"plusResult:{plusResult},minusResult:{minusResult}");
        }
出力
plusResult:101,minusResult:99

ちなみに利点は、「必ず値がセットされる事が保証される」 です。今回とは趣旨が違うので詳細は省きますが・・・1

Try**

さて、自分でoutキーワードを使ったメソッドを作らないかもしれませんが、.netで用意されているクラスのメソッドにoutキーワードが使われている場合ももちろんあります。

(個人的に)一番使うであろうメソッドは Try系メソッドです。

例えば、文字列をintに変換しようとした場合、int.Parse("100");のように、パースメソッドを呼びます。
しかし、例えばint.Parse("ほげ"); こんなことをしたら変換に失敗してExceptionが発生してしまいます。
try~catchで括ってもいいですが、先に変換可能かどうか調べつつ変換できないのかな?。それ既にありますよ!!

bool int.TryParse(string s,out int result);

こんなのが用意されてます。 変換が出来る場合はメソッドの戻り値がtrueになり、さらにresultに結果が入ります。2
出ました!outキーワード!!

使う場合は

int result = 0;
if(int.TryParse("ほげー",out result) == false){
    Debug.Log("変換できませんでした");
}else{
    Debug.Log($"変換結果:{result}");
}

こんな感じで、if文の中に入れて、戻り値で分岐させるのがよく見る使い方です。

#辛かったこと
さて、このoutキーワードを使うのに辛かった事が2点あります。

①変数を先に宣言しなくてはいけない
②変数の宣言に(仕組み上)varが使いづらい

①はまぁ、格納先なので、当たり前として。 ②は何かというと、varというやつは右辺から左辺の型を推論してくれる機能です。右辺がintならintstringならstringに宣言してくれているわけです。

では、今回のケースに無理矢理varを使うとしたらどうなるでしょう。 intなので・・・。

var plusResult = 0;
var minusResult = 0;

ってやります? この0はどこから来たんだよという話で。
varを使いたいがために差し当たりの無い値0を持ってきた に過ぎなく、手段と目的が逆転してます。

#前置きが長かったね
それが、なんと、このように書けるようになります。

if (int.TryParse("ぼげー", out var result) == false)
{
    Debug.Log("変換できませんでした");
}
else
{
    Debug.Log($"result:{result}");
}

やったー! 宣言が中に入った!! そう、 out に指定する変数をその場で宣言できるようになりました!

注目してほしいのは、この

else
{
    Debug.Log($"result:{result}");
}

この、elseブロックにも result が使われている事です。

for文の中で宣言した変数なんかは

for(var i = 0;i < 10;++i)
{
  Debug.Log(i); //スコープ内
}
Debug.Log(i); //スコープ外なのでエラー

このようにブロックがスコープ(生存範囲)になりますが、このout変数宣言はその式が含まれているブロックがスコープになります。
まぁ、 1行前に変数宣言があるのと同じ。 と考えておけば大体OKです。

##TryGetComponent
さてさて、ようやく TryGetComponent の話に移れます。
最初にも言いましたが、 Unity 2019.2 から TryGetComponent という GetComponent 相当のメソッドが追加されています。

bool TryGetComponent<T>(out T component)

今まで GetComponent は指定したComponentが取得できなかった場合は null が返ってくるので、それによって成否を判断していました。
それが、他の Tryメソッド 同様、 成否はbool を返すようになり、 out変数 に直接結果を入れてくれる仕組みです。
また出た!outキーワード!! デモモウコワクナイ!

具体的には今まで

        var sr = GetComponent<SpriteRenderer>();
        if (sr != null) //nullかどうかで成否判定
        {
            sr.color = Color.white;
        }

こうだったのが、TryGetComponentが追加されたこと、そして out変数宣言 が使えるようになったことで

        if (TryGetComponent<SpriteRenderer>(out var sr)) //TryGetComponentの戻り値自体が 成否、out宣言で中身が入る
        {
            sr.color = Color.white;
        }

このようになります。

どっちが速いかーーーというと、正直あんまり差が無いらしいんですが・・・。(テラシュールブログ様参照)

#ちなみに

TryGetComponent<SpriteRenderer>(out var sr))

これは、Genericsの型パラメータに明示的に SpriteRenderer を渡しているので、 var sr というように、 var型推論 が効いている状態です。

逆に、使用される型から型パラメータを型推論 することも出来るので、

TryGetComponent(out SpriteRenderer sr))

こう書いてもよいです。 
どちらを使うかは完璧好みでしょうから、お好きな方を使えばいいと思います。 僕は前者です。

え?

TryGetComponent(out var sr))

はダメなのかって? 何をGetComponentするおつもりですか・・・?

##補足

そういえば、今のところあまり見ませんが、

public class Main : MonoBehaviour
{
    private SpriteRenderer sr;

    void Start()
    {
        if (TryGetComponent(out sr)) //メンバ変数に格納
        {
            sr.color = Color.white;
        }
    }
}

こういう書き方も出来るはずですよね。 変数宣言と TryGetComponent の箇所が離れすぎていると、何をGetComponentしたのか一瞬戸惑いそうですけれど。

しかし、これは案外アリかもしれません。

僕はよく、Inspectorで自分自身が持っているComponentをセットさせる時に、(RequireComponentなど)

  • 実行時に GetComponentは重いらしいから、あんまりやりたくないな。
  • RequireComponent指定してあって、確実にそのComponentがあるってわかってるのにInspectorでわざわざドラッグ&ドロップとかかったるいな。

って時に、

using UnityEngine;

[RequireComponent(typeof(SpriteRenderer))] //SpriteRendererを要求
public class ForceWhite : MonoBehaviour
{
    [SerializeField]
    private SpriteRenderer spriteRenderer;

    //このMonobehaviorをアタッチした瞬間、または コンテキストメニューから `Reset` を選んだ時に呼ばれる
    void Reset()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    void Start()
    {
        spriteRenderer.color = Color.white;
    }
}

Resetメソッド を使って、半自動的にInspector参照を解決させるんですが
アタッチした瞬間、SpriteRendererが自動でセットされるの図

    //このMonobehaviorをアタッチした時、または コンテキストメニューから `Reset` を選んだ時に呼ばれる
    void Reset()
    {
        TryGetComponent(out spriteRenderer); //呼びっぱなし
    }

そういう用途(Editor上でしか走らない箇所)であれば、こうやっちゃうのも悪くないのでは???(マサカリ案件)

#なんにせよ
用法用量を守って、ステキなUnityライフを。 それでは。

  1. 気になる人は、 PlusMinusOne メソッドの呼び出しをコメントアウトしてみてください。 エラーになります。

  2. ほかにも float.TryParseDateTime.TryParse など、Parse系メソッドはほぼ確実に TryParseメソッド が用意されています。

30
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?