Подтвердить что ты не робот

Самый быстрый способ проверить, является ли тип blittable?

В моем сериализаторе/десериализаторе у меня есть следующий фрагмент:

    if (element_type.IsValueType && collection_type.IsArray)
    {
        try
        {
            GCHandle h = GCHandle.Alloc(array_object, GCHandleType.Pinned);
            int arrayDataSize = Marshal.SizeOf(element_type) * c.Count;
            var array_data = new byte[arrayDataSize];
            Marshal.Copy(h.AddrOfPinnedObject(), array_data, 0, arrayDataSize);
            h.Free();
            WriteByteArray(array_data);

            return;
        }
        catch (ArgumentException)
        {
            //if the value type is not blittable, then we need to serialise each array item one at a time
        }
    }

Цель состоит в том, чтобы попытаться написать массив типов значений потоку наиболее эффективным способом (то есть просто содержимым в виде группы байтов).

Проблема возникает, когда тип является типом значения, но не является blittable, а Alloc() терпит неудачу. В настоящий момент исключение поймано и управление передано в код, который имеет дело с массивом, как если бы он состоял из ссылочных типов.

Однако эта проверка (из-за бросания и ловушки исключения, который, как я понимаю, очень медленный), является серьезным узким местом из-за количества типов значений, которые встречаются в моем приложении. Поэтому мне интересно, , что самый быстрый способ проверить, является ли тип blittable?

4b9b3361

Ответ 1

Я использую общий класс для кэширования результатов. Тестирование выполняется таким же образом (пытаясь выделить закрепленный дескриптор).

public static class BlittableHelper<T>
{
    public static readonly bool IsBlittable;

    static BlittableHelper()
    {
        try
        {
            // Class test
            if (default(T) != null)
            {
                // Non-blittable types cannot allocate pinned handle
                GCHandle.Alloc(default(T), GCHandleType.Pinned).Free();
                IsBlittable = true;
            }
        }
        catch { }
    }
}

Ответ 2

Текущий ответ работает на случай опроса, но, согласно спецификации, массивы типов смягчаемых значений также являются самими мягкими типами. Расширенный метод Ondřej, поэтому он учитывает это, а также работает для ссылочных типов:

public static bool IsBlittable<T>()
{
    return IsBlittableCache<T>.Value;
}

public static bool IsBlittable(Type type)
{
    if(type.IsArray)
    {
        var elem = type.GetElementType();
        return elem.IsValueType && IsBlittable(elem);
    }
    try{
        object instance = FormatterServices.GetUninitializedObject(type);
        GCHandle.Alloc(instance, GCHandleType.Pinned).Free();
        return true;
    }catch{
        return false;
    }
}

private static class IsBlittableCache<T>
{
    public static readonly bool Value = IsBlittable(typeof(T));
}

Как побочный эффект, это возвращает (хотя и правильно) false для string, потому что GetUninitializedObject не может его создать. Предполагая, что Alloc действительно проверяет blittability (кроме string), это должно быть надежным.

Ответ 3

Превосходный код @IllidanS4 на этой странице неверно возвращает false для массивов, где элемент является blittable отформатированным типом, что означает, что массив также является blittable. Исходя из этого примера, я исправил эту проблему и добавил обработку для еще нескольких неправильных случаев, таких как:

  • T[] где T: форматированный тип (только что упомянутый)
  • зубчатые массивы int[][][]...
  • enums (но не System.Enum ittself)
  • интерфейсы, абстрактные типы
  • общие типы (никогда не горит).

Я также добавил, что делались для того, чтобы избежать дорогостоящего блока Exception немного более исчерпывающего и запустить единичные тесты для всех типов, которые я мог придумать.

public static bool IsBlittable(this Type T)
{
    while (T.IsArray)
        T = T.GetElementType();

    bool b;
    if (!((b = T.IsPrimitive || T.IsEnum) || T.IsAbstract || T.IsAutoLayout || T.IsGenericType))
        try
        {
            GCHandle.Alloc(FormatterServices.GetUninitializedObject(T), GCHandleType.Pinned).Free();
            b = true;
        }
        catch { }
    return b;
}

Хороший механизм кэширования из другого ответа должен использоваться как есть.