【C#】Type.GetType(string)するときはアセンブリ情報がないほうが早い

C#
このエントリーをはてなブックマークに追加

ここ最近作り始めたライブラリで需要があったのでType.GetType(string)のベンチマークを取ってみました。

ことの発端

Type型を一度stringにして保存し、あとで復元するという処理があったのですが、Type型には型情報をstring化するのに二つのプロパティがあります

  • Type.AssemblyQualifiedName
    これは単純にアセンブリ情報の詳細まで付与されます。
    string型ならSystem.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
    List<string>型ならSystem.Collections.Generic.List`1[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
  • Type.FullName
    こちらは基本的には名前空間.型名までしか返しません。 しかしジェネリクスの型パラメーターがあると、型パラメーターのところはAssemblyQualifiedNameと同様な表現にされて返ってきます。
    string型ならSystem.String
    List<string>型ならSystem.Collections.Generic.List`1[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]

そこでアセンブリ情報あるのと無いのどちらが早いの?という疑問が生まれたのでLet’s ベンチマーク。

まずはFullNameを取る

ベンチマークでは文字列直接挿入して余計な部分は省きましたが、プログラム上で使えないので、(自分のために)純粋なFullNameを取るコードも書きました。

        public static class TypeCache<T>
        {
            public static readonly string FullNameWithAssembly;
            public static readonly string FullName;

            static TypeCache()
            {
                FullNameWithAssembly = typeof(T).AssemblyQualifiedName;
                FullName = toFullName(typeof(T));
            }

            private static string toFullName(Type type)
            {
                var sb = new StringBuilder();
                sb.Append(type.Namespace);
                sb.Append('.');
                sb.Append(type.Name);

                if (type.GenericTypeArguments.Length <= 0)
                {
                    return sb.ToString();
                }

                sb.Append('[');
                int count = 0;
                foreach (var genericType in type.GenericTypeArguments)
                {
                    if (0 < count)
                    {
                        sb.Append(',');
                    }
                    sb.Append('[');
                    sb.Append(toFullName(genericType));
                    sb.Append(']');
                    count++;
                }
                sb.Append(']');
                return sb.ToString();
            }
        }

generics static classで型パラメーターごとにキャッシュを働かせます。こっちのほうが毎回typeof(T)するより早いです。 使い方はstring fullName = TypeCache<List<string>>.FullName;みたいな感じです。

肝心のベンチマーク

先ほど言ったようにベンチマークでは文字列直接挿入してます。
ベンチマークではType.GetType(string)をDictionary<string,List<string>>とstringのアセンブリ情報付きとアセンブリ情報のアセンブリ名だけ付いてるものとただのFullNameの計6通りで測りました。
(メソッド名は順に*FullNameWithAssemblyDetail, *FullNameWithAssembly, FullNameとつけ*にはDictionary<string,List<string>>のほうはGenericsType, stringのほうはTypeと付けてます)
詳細なソースコードはgistに上げてます。

結果:

Dictionary<string,List<string>>, string共にFullNameのほうがアセンブリ情報付きの倍近い速度が出ました。

単純に文字列の長さでここまで差が出るとは思えないので、内部的に何らかのロス(おそらくアセンブリチェック)があると思われます。

アセンブリ情報まで付けたほうがプログラム的に優しいかなぁと思ってたりしましたが、ベンチマーク上では遅いことと、名前空間はそう簡単に重ならないということで、作成中のライブラリではFullNameでType.GetType(string)しようかなと思います。