ねののお庭。

かりかりもふもふ。

EqualityComparer<T>.Defaultの実装を追ってみる。

気になりません?

純粋にどうやって生成してるのか疑問に思ったので気が済むまで追ってみる。 完全に備忘録でありいろいろ雑。

using System.Runtime.CompilerServices;

namespace System.Collections.Generic
{
    [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
    [TypeDependency("System.Collections.Generic.ObjectEqualityComparer`1")]
    [Serializable]
    public abstract class EqualityComparer<T>  : IEqualityComparer, IEqualityComparer<T> 
    {
        public static EqualityComparer<T>  Default { [Intrinsic] get; } = (EqualityComparer<T> ) ComparerHelpers.CreateDefaultEqualityComparer(typeof (T));

        public abstract bool Equals(T x, T y);

        public abstract int GetHashCode(T obj);

        internal virtual int IndexOf(T[] array, T value, int startIndex, int count)
        {
            int num = startIndex + count;
            for (int index = startIndex; index < num; ++index)
            {
                if (this.Equals(array[index], value))
                    return index;
            }
            return -1;
        }

        internal virtual int LastIndexOf(T[] array, T value, int startIndex, int count)
        {
            int num = startIndex - count + 1;
            for (int index = startIndex; index > = num; --index)
            {
                if (this.Equals(array[index], value))
                    return index;
            }
            return -1;
        }

        int IEqualityComparer.GetHashCode(object obj)
        {
            if (obj == null)
                return 0;
            if (obj is T)
                return this.GetHashCode((T) obj);
            ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison);
            return 0;
        }

        bool IEqualityComparer.Equals(object x, object y)
        {
            if (x == y)
                return true;
            if (x == null || y == null)
                return false;
            if (x is T && y is T)
                return this.Equals((T) x, (T) y);
            ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison);
            return false;
        }
    }
}

16行目が生成しているところ。関数はこんな感じ。

internal static object CreateDefaultEqualityComparer(Type type)
{
    object obj = (object) null;
    RuntimeType runtimeType = (RuntimeType) type;
    if (type == typeof (byte))
        obj = (object) new ByteEqualityComparer();
    else if (typeof (IEquatable<> ).MakeGenericType(type).IsAssignableFrom(type))
        obj = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof (GenericEqualityComparer<int> ), runtimeType);
    else if (type.IsGenericType)
    {
        if (type.GetGenericTypeDefinition() == typeof (Nullable<> ))
        obj = ComparerHelpers.TryCreateNullableEqualityComparer(runtimeType);
    }
    else if (type.IsEnum)
        obj = ComparerHelpers.TryCreateEnumEqualityComparer(runtimeType);

    return obj ?? RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof (ObjectEqualityComparer<object> ), runtimeType);
}

いろいろ知らない。 Typeはこう。

public abstract class Type : MemberInfo, IReflect

でRuntimeTypeはこう。internalらしい。

internal class RuntimeType : TypeInfo, ICloneable

でこのTypeInfoが

public abstract class TypeInfo : Type, IReflectableType

なるほどRuntimeTypeはランタイムの中だけで使われる具象っぽい。

一番始めにbyte型か確認してる。これはいい。 次に

(typeof (IEquatable<> ).MakeGenericType(type).IsAssignableFrom(type))

これはなにか。

(typeof (IEquatable<> ).MakeGenericType(type)

でIEquatableという型を作ってIsAssignableFromでtype型がMakeGenericTypeで生成された型(これはインターフェース)が実装されているか、を調べている。 typeがintの場合、Int32の型の定義はこんな感じであり、

public struct Int32 : IComparable, IConvertible, IFormattable, IComparable<int> , IEquatable<int> , ISpanFormattable

IEquatableが実装されているためtrueが帰ってくる形になる。 リフレクション自分じゃそんなに使わないのでよく知らないのですが、いろいろできるんですねぇ。Typeから生えてるメソッドの充実ぶりに驚いている。 そのあとはジェネリック型かみて、それがNullable型かどうか、Enum型かどうかを見てますね。 実際にobjを作っているのは

ByteEqualityComparer();

RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof (GenericEqualityComparer<int> ), runtimeType);
RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof (ObjectEqualityComparer<object> ), runtimeType);

ComparerHelpers.TryCreateNullableEqualityComparer(runtimeType);

ComparerHelpers.TryCreateEnumEqualityComparer(runtimeType);

の4つであることがわかる。byteとNullableは特別扱いなんですねー。 最終的にobjがnullだった場合、typeof (ObjectEqualityComparer<object>)が渡されるのは分かる(Object.Equalsとかが使われるからだろう)のだが、インターフェースが実装されていた場合、typeof (GenericEqualityComparer<int>)が渡されているのは一体どういうことだろう。なぜint。みてみると

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object CreateInstanceForAnotherGenericParameter(
RuntimeType type,
RuntimeType genericParameter);

\\internal static extern//

C#で書かれておらず、C++で実装されているらしいですねこのやろう...。 しかし.NET Coreはオープンソースです。ならばソースを見てやろうじゃないかとなるわけです。clrにかかれていると思われるので、ここでCreateInstanceForAnotherGenericParameterで検索します。 するとこれに引っかかります。 引用すると

FCFuncElement("CreateInstanceForAnotherGenericParameter", RuntimeTypeHandle::CreateInstanceForGenericType)

まぁおそらく第一引数の文字列が呼び出し名で、第二引数が呼び出される実際のものなのだろう。ということで次はCreateInstanceForGenericTypeをリポジトリ内で検索する。そうするとこれに引っかかる。引用しつつコメント書きながら読んでみます。

FCIMPL2(Object*, RuntimeTypeHandle::CreateInstanceForGenericType, ReflectClassBaseObject* pTypeUNSAFE, ReflectClassBaseObject* pParameterTypeUNSAFE) {
    FCALL_CONTRACT;

    struct _gc
    {
    OBJECTREF rv;
    REFLECTCLASSBASEREF refType;
    REFLECTCLASSBASEREF refParameterType;
    } gc;

    gc.rv = NULL; //return value?
    gc.refType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTypeUNSAFE);//(RuntimeType) typeof (GenericEqualityComparer<int> )
    gc.refParameterType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pParameterTypeUNSAFE);//runtimeType

    MethodDesc* pMeth;
    TypeHandle genericType = gc.refType-> GetType();//(RuntimeType) typeof (GenericEqualityComparer<int> )

    TypeHandle parameterHandle = gc.refParameterType-> GetType();//runtimeType

    _ASSERTE (genericType.HasInstantiation());//RuntimeType) typeof (GenericEqualityComparer<int> )側がInstantiationを持っているか(生成できるかとかそういうニュアンスあるのかなぁ)

    HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);//gcをプロテクトしてるのかな。

    // 多分これがGenericEqualityComparer<runtimeType> 型を生成してそう。Canonical(正典の、教会法に基づく、規準的な、標準的な)
    //objectとintで特に場合分けとかしてなさそうなのでこれの内部でなにかしてそうだけど...うーん。
    TypeHandle instantiatedType = ((TypeHandle)genericType.GetCanonicalMethodTable()).Instantiate(Instantiation(&parameterHandle, 1));

    // Get the type information associated with refThis
    MethodTable* pVMT = instantiatedType.GetMethodTable();//virutual method table pointerかな
    _ASSERTE (pVMT != 0 &&  !instantiatedType.IsTypeDesc());
    _ASSERTE(!(pVMT-> GetAssembly()-> IsDynamic() && !pVMT-> GetAssembly()-> HasRunAccess()));
    _ASSERTE( !pVMT-> IsAbstract() ||! instantiatedType.ContainsGenericVariables());
    _ASSERTE(!pVMT-> IsByRefLike() && pVMT-> HasDefaultConstructor());

    pMeth = pVMT-> GetDefaultConstructor();            
    MethodDescCallSite ctor(pMeth);

    // We've got the class, lets allocate it and call the constructor

    // Nullables don't take this path, if they do we need special logic to make an instance
    _ASSERTE(!Nullable::IsNullableType(instantiatedType));//Nullableだと怒られる(だからかC#側で事前に弾いているね)
    gc.rv = instantiatedType.GetMethodTable()-> Allocate();//関数テーブルからアロケーションしてる

    ARG_SLOT arg = ObjToArgSlot(gc.rv); 

    // Call the method
    TryCallMethod(&ctor, &arg, true);

    HELPER_METHOD_FRAME_END();
    return OBJECTREFToObject(gc.rv);
}
FCIMPLEND //FCIMPLEND...function implementation endの略かな。

わからんw

関数テーブルからAllocateしていたりInstantiateしてるんですね。よくわからないけど面白い。これ以上に深追いは時間が無限に溶ける気がするので止めておく。。。

結論

早々にCLRC++の関数呼び出しにぶつかってしまい、消化不良...。もんもん。