Самый быстрый способ проверить, является ли тип 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?
Ответы
Ответ 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;
}
Хороший механизм кэширования из другого ответа должен использоваться как есть.
Ответ 4
Используйте http://msdn.microsoft.com/en-us/library/system.type.islayoutsequential.aspx и http://msdn.microsoft.com/en-us/library/system.type.isexplicitlayout.aspx:
element_type.IsValueType && collection_type.IsArray && (element_type.IsLayoutSequential || element_type.IsExplicitLayout)