Что такое Type.GUID и как оно относится к Type.Equals()?

Я столкнулся с каким-то интересным поведением, пытаясь сравнить экземпляр System.RuntimeType с общим типом TOut:

Type runtimeT = methodInfo.ReturnType; // get RuntimeType using reflection
Type genericT = typeof(TOut);

// This condition fails because runtimeT doesn't 
// seem to include an assembly qualified name
if(runtimeT.Equals(genericT)) { ... }

Вот мои доказательства:

screenshot of debug watch window - Equals() returns false on types, true on GUIDs

Отказ от ответственности: Я точно не знаю, что такое GUID в контексте CLR/type-system, за исключением того, что аббревиатура обозначает глобальный уникальный идентификатор. Возможно, это имя меня вводит в заблуждение.

Предположение: Я предполагаю, что GUID Type однозначно идентифицирует полностью квалифицированный тип, включая AssemblyQualifiedName, который отсутствует в factoryInfo.ReturnType на скриншоте (значение null.)

Я ошибаюсь в своем предположении?

  • Да. Что действительно представляет GUID GUID и как оно используется?

  • Нет: почему бы Equals() не было реализовано путем сравнения GUID?

Ответ 1

Одна из причин, по которой Type.GUID не используется, поскольку сравнение для Equals заключается в том, что это элемент, управляемый пользователем. Например, я могу диктовать GUID моего interface, выполнив следующие

[Guid("2bfd006d-94b9-43af-843f-5b32f7567086")]
interface IFoo { ... }

Это необходимо для создания интерфейсов для COM-взаимодействия. Если Equals полагалось, что GUID является уникальным для типа, то данный разработчик может нанести ущерб системе, указав свой тип тем же GUID, что и сказать string, int и т.д.

Ответ 2

Чтобы немного рассказать о Джаре (полностью правильный):

В мире COM каждый интерфейс идентифицируется глобально уникальным идентификатором. В COM нет такой вещи, как "изменение" интерфейса; интерфейсы должны быть одинаковыми навсегда. Вместо этого вы создаете новый интерфейс и даете ему новый GUID. Любые два интерфейса, которые отличаются, должны иметь разные GUID. Равномерность интерфейса определяется как равенство GUID в COM.

В мире .NET тип равенства более сложный. С одной стороны, тип связан с конкретной сборкой. Но не только это! Если вы дважды загрузите одну и ту же сборку (скажем, один раз по имени своей сборки и один раз по месту на диске) и спросите две сборки для "того же" типа, вы получите два разных типа объектов, и они не будут сравниваться как равные, хотя очевидно, что они имеют один и тот же идентификатор GUID.

Ясно, что это главная отправная точка;.NET и COM глубоко несовместимы в этом отношении. Что произойдет, если вмешательство должно произойти? Как-то COM и .NET должны договориться о некоторых правилах для того, как типы сравниваются для равенства, когда оба игрока играют в одном процессе. (Потому что .NET вызывает COM-код или наоборот.)

Таким образом, что вы можете сделать в .NET, скажем, что "этот тип связан с этим GUID". Когда применяются правила COM, COM-код будет сравнивать два типа для равенства, сравнивая идентификаторы GUID, потому что это означает, что означает равенство в мире COM.

В .NET типы сравниваются для равенства, используя обычные правила для .NET.

Это создает значительную потенциальную проблему в общем сценарии. Предположим, вы написали .NET-программу, которая взаимодействует с большой сложной библиотекой COM. Предположим, что вы создали полностью неслучайный пример, предположим, что вы создали управляемое расширение для Word, которое имеет абсолютно огромную COM-поверхность. Эта область поверхности открыта для .NET-мира через Primary Interop Assembly, которая содержит типы "dummy", которые имеют все те же идентификаторы GUID, что и совместимые интерфейсы в мире COM. Затем код .NET можно записать, чтобы поговорить со слоем COM через объекты "dummy", которые обращаются к COM как объекты соответствующего типа интерфейса, и посмотрите, что .NET-код является объектами соответствующего типа .NET.

Так что все работает отлично. И затем вы отправляете свою библиотеку .NET клиентам, и вы понимаете, что Word не отправляет PIA клиентам автоматически. Скорее, вы должны отправить свою PIA. Что огромно.

Таким образом, появилась функция "no PIA" на С# 4. В С# 4 вы можете сгенерировать расширение Word, которое делает внутреннюю копию только частей слова PIA, которое оно фактически использует. Что обычно намного меньше. Затем вы можете перераспределить свою библиотеку расширений без перераспределения большого PIA.

Но это сразу представляет проблему. Предположим, есть две такие библиотеки, и они хотят общаться друг с другом, используя типы интерфейса, которые являются общими для обоих, в PIA? С точки зрения .NET, типы относятся к каждой сборке; копии типов PIA в каждой библиотеке одинаковы с точки зрения COM, но отличаются от точки зрения .NET.

Поэтому мы добавили специальную функцию в v4 CLR. В этой ситуации два типа в двух разных сборках (и тип PIA, если он присутствует!) Унифицированы CLR и считаются равными по GUID, что соответствует поведению COM.

Детали, конечно, намного сложнее, чем простой эскиз. Я хочу сказать, что вы открываете здесь огромную банку червей; как GUID взаимодействуют с равенством типов - это глубокий и сложный предмет, который мало кто понимает полностью.

Ответ 3

Вы не должны полагаться на свойство GUID System.Type для сравнения типов. В частности, в межпроцессной связи (WCF, Remoting) руководство может быть не таким же (если оно не указано вручную в примере JaredPar).

Внутри, Type.Equals использует RuntimeTypeHandle для сравнения равенства.

Я не уверен, почему ваше сравнение равенства выше не работает. Это не должно. В самом простом примере ниже, равно возвращается true.

    static void Main(string[] args)
    {
        Test<object>();
    }

    static object Test<T>()
    {
        var currentMethod = ((MethodInfo) MethodBase.GetCurrentMethod());
        var oType = currentMethod.ReturnType;
        var genericType = typeof (T);
        var equal = oType.Equals(genericType); // result is true.
        return null;
    }

Мой случайный снимок в темном предположении заключается в том, что вы используете Entity Framework с поддержкой прокси-сервера? В этом случае общий параметр T IEntitySet является динамически сгенерированным типом, созданным для вас Entity Framework... Если это так, вы должны получить желаемый результат, сравнив общие аргументы индивидуально:

Ответ 4

GUID - это случайная сгенерированная последовательность байтов (по умолчанию 16), которая полузащищена никогда не повторяться - не через компьютеры или время. Полу-гарантировано, потому что возможность повторения существует, но она настолько незначительна, что не рассматривается.

Из-за этого большинство программистов используют GUID, когда им нужно предоставить идентификатор для чего-то, где они беспокоятся о ID, имеющем столкновение с другим идентификатором, который может оказаться существующим в той же сфере (идентификаторы запущенных программ или таблицы строки или миллион других вещей)

Но, короче говоря, вы можете придумать GUID как случайное число, представляющее идентификатор, длиной 16 байтов и никогда не будет генерироваться дважды.