RectTransformをなるはやで取得したい

Unity標準のUIといえば俗にいう「uGUI」です。

これらuGUIコンポーネントは通常の`Transform`ではなくて、`RectTransform`を使う必要があります。

ところで。

みなさん、`RectTransform`をスクリプトで取得する時には`GetComponent<RectTransform>()`ってやってるんじゃないかと　思います。
でも知ってました？`RectTransform`って`Transform`のサブクラスなんですよ。

なので、

```csharp
var rectTransform1 = GetComponent<RectTransform>();
var rectTransform2 = transform as RectTransform;
```

実はこの2つとも正しく`RectTransform`が取得できます。

すると気になるのが**速度**です。　『遅い遅い、何やってんだ、しっかりしろ、どうなってるんだ。』　と評判の`GetComponent`が出てくるからには速度が気になります。

というわけで調べてみました。

## GetComponent vs Cast vs Cache

```csharp:検証コード
using UnityEngine;
using UnityEngine.Profiling;

public class SpeedCheck : MonoBehaviour
{
    private readonly int max = 10000;

    private CustomSampler samplerMemberCache;
    private CustomSampler samplerGetComponent;
    private CustomSampler samplerAsCast;

    private RectTransform cachedRectTransform;

    void Start()
    {
        //サンプラー用意
        samplerGetComponent = CustomSampler.Create("GetComponent");
        samplerAsCast = CustomSampler.Create("AsCast");
        samplerMemberCache = CustomSampler.Create("MemberCache");

        //cache
        cachedRectTransform = GetComponent<RectTransform>();
    }

    void Update()
    {
        //GetComponent
        samplerGetComponent.Begin();
        for(var i = 0;i < max;++i)GetComponent<RectTransform>().Rotate(0,0,Time.deltaTime);
        samplerGetComponent.End();

        //as演算子
        samplerAsCast.Begin();
        for (var i = 0; i < max; ++i) (transform as RectTransform).Rotate(0, 0, Time.deltaTime);
        samplerAsCast.End();

        //メンバ変数キャッシュ
        samplerMemberCache.Begin();
        for (var i = 0; i < max; ++i) cachedRectTransform.Rotate(0, 0, Time.deltaTime);
        samplerMemberCache.End();
    }
}
```

結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/5bd27734-7228-eb35-4e17-9f049e63f5da.png)

GetComponent:2.73ms
AsCast:2.53ms
MemberCache:2.28ms


うーん。　予想を裏切らない、

**「GetComponet使うなら、asでキャストしたほうが良い。　でもやっぱりキャッシュが一番速いよね」**

という思った通りの結果となりました。 

## + ExtendMethodAsCast vs PropertyCache

（まぁ、メンバ変数へのキャッシュが最速なのはさておき） As演算子でキャストするのは良いんですが、そのまま使おうとすると括弧を一つ挟まなくてはいけないので若干面倒です。

なので、こういう拡張メソッドを用意したらどうでしょう

```csharp:as演算子拡張メソッド版
public static class RectTransformExtension
{
    public static RectTransform ToRectTransform(this Transform t)
    {
        return t as RectTransform;
    }
}
```

それと、メンバ変数へのキャッシュはうっかりStart(Awake)でキャッシュを入れ忘れたらまずいですよね。
なので、こういうことをよくやります。

```csharp:初回アクセス時にキャッシュを自動でするマン（プロパティキャッシュ)
    private RectTransform _cachedRectTransform;
    public RectTransform RectTransform => 
        _cachedRectTransform != null ? _cachedRectTransform : (_cachedRectTransform = transform as RectTransform);
```

さてこれらは速いんでしょうか。(「プロパティキャッシュ」と本当に呼ぶかどうかは知りません。勝手につけました)

検証コードに以下を追加して、再検証してみます。

```cs
        //as演算子 拡張メソッド版
        samplerMethodExtendAsCast.Begin();
        for (var i = 0; i < max; ++i) transform.ToRectTransform().Rotate(0, 0, Time.deltaTime);
        samplerMethodExtendAsCast.End();

        //プロパティキャッシュ
        samplerPropertyCache.Begin();
        for (var i = 0; i < max; ++i) RectTransform.Rotate(0, 0, Time.deltaTime);
        samplerPropertyCache.End();
```

結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/71263493-fa26-78c6-d548-1cef08427ad1.png)

GetComponent:2.95ms
AsCast:2.61ms
MemberCache:2.29ms
拡張メソッドAsCast:2.66ms
プロパティキャッシュ:2.68ms

拡張メソッドの方は、メソッド呼び出しで若干オーバーヘッドが出るので遅くなることは想定してましたが、微々たるものですね。悪くないです。

ところが、予想に反してプロパティキャッシュがやたら遅いです。Castに負けるってありえなくないですか？なんででしょう？？？？？

##(UnityEngine.Object)のnullチェック! またお前か！

普段からunityをお使いの紳士・淑女はご存知の方も多いと思います。　UnityEngine.Objectを継承しているクラスにおいて、**nullチェックはnullチェックではなく、nullチェック＋生存確認**です。

なので、

```csharp
    public RectTransform RectTransform => 
        _cachedRectTransform != null ? _cachedRectTransform : (_cachedRectTransform = transform as 
```

の

```cs
        _cachedRectTransform != null
```

は

```cs
        _cachedRectTransform.IsAlive() //こんなメソッドはありません。あくまでも概念です
```

をやっているようなもので、遅い原因はコイツです。

ここでは、別に生存を取りたいわけではなくて、**キャッシュしてあるかどうか**を判断するためのnullチェックなので、いってしまえばbool的な使い方です。
試しにちょっとソースコードが太りますが、プロパティキャッシュを以下のように変更してみます。

```cs
    private RectTransform _cachedRectTransform;
    //    public RectTransform RectTransform => _cachedRectTransform != null ? _cachedRectTransform : (_cachedRectTransform = transform as RectTransform); //これは辞めて
    private bool isCached;
    public RectTransform RectTransform
    {
        get
        {
            if (!isCached)
            {
                _cachedRectTransform = transform as RectTransform;
                isCached = true;
            }
            return _cachedRectTransform;
        }
    }
```

結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/e61119c4-d842-197e-72aa-8968d1a58e10.png)

GetComponent:3.16ms
AsCast:2.53ms
MemberCache:2.24ms
拡張メソッドAsCast:2.73ms
プロパティキャッシュ:2.37ms

GetComponent > 拡張メソッドAsCast > AsCast > プロパティキャッシュ > MemberCache

概ね想像通りの結果となりました。

なお。今回はわかりやすさ優先でわざわざboolの変数(isCached)を用意しましたが、
`??`(null合体演算子) を使って

```cs
    private RectTransform _cachedRectTransform;
    public RectTransform RectTransform => _cachedRectTransform ?? (_cachedRectTransform = transform as RectTransform);
```

こう書いた方が効率が良いです。(タイピング量的にも、処理的にも)

一応、計測結果もペタリ
![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/42bfe69a-604a-f613-7dab-5d0d2ed06cd3.png)

##まとめ
まぁ、実の所`GetComponent`もそんな禿げ上がるほど遅くはないので、よほどピーキーなチューニングが必要な環境でもない限り使ったっていいんじゃないの？という気もします。（マサカリ案件）
次点でプロパティ使ったキャッシュかなーと、というのもただでさえ汚いStartやAwakeをあまり汚したくないのと、プロパティキャッシュ方式ならスニペットとかで簡単にコード挿入できるかなーとか。(あくまでも個人的な意見です。 ここら辺のキャッシュを加味した MonoBehaviour継承のクラスを使うのはまぁ好き好きで)（マサカリ案件その２）

何にせよ、Unityには素敵なProfilerが最初から付いてるので、（心と時間に）余裕があれば計測しましょう。
知らないで使うのと、知って使うのとでは色々と見える世界が変わってくると思います。
それでは。