Самый эффективный способ проверить, является ли объект типом значения

#c# #.net #performance #value-type

#c# #.net #Производительность #тип значения

Вопрос:

ПРЕДУПРЕЖДЕНИЕ: ЭТОТ КОД ОТСТОЙ, СМОТРИТЕ КОММЕНТАРИИ ЭНТОНИ

Что быстрее?

1.

   public bool IsValueType<T>(T obj){
       return obj is ValueType;
  }
  

2.

   public bool IsValueType<T>(T obj){
       return obj == null ? false : obj.GetType().IsValueType;
  } 
  

3.

   public bool IsValueType<T>(T obj){
       return default(T) != null;
  }
  

4. Что-то еще

Комментарии:

1. Действительно ли производительность имеет значение, потому что это действительно микрооптимизация

2. Методы 2 и 3 в том виде, в каком они написаны, недопустимы. obj == null || вернет true для ссылочных типов. default(T) != null вернет false для Nullable<T> структур.

3. Ваша правка в методе 2 по-прежнему будет недействительной. obj != null || вернет true для объектов ссылочного типа, отличных от null.

4. Поскольку я слишком критичен, э-э, я имею в виду полезен, метод 1 не любит обнуленные Nullable<T> объекты. int? bar = null; Передайте это через функцию, вы получите false . (Честно говоря, не ожидал этого.)

5. Метод 2 последняя правка. return obj == null ? false : ... все еще представляет проблему для Nullable<T> .

Ответ №1:

На самом деле вы не тестируете объект — вы хотите протестировать тип. Чтобы вызвать их, вызывающий должен знать тип, но … meh. Учитывая подпись <T>(T obj) , единственный разумный ответ:

 public bool IsValueType<T>() {
    return typeof(T).IsValueType;
}
  

или если мы хотим использовать пример объекта для целей вывода типа:

 public bool IsValueType<T>(T obj) {
    return typeof(T).IsValueType;
}
  

для этого не требуется боксирование ( GetType() это боксирование), и с Nullable<T> ним не возникает проблем. Более интересный случай — это когда вы передаете object

  public bool IsValueType(object obj);
  

здесь у нас уже есть серьезные проблемы с null , поскольку это может быть пустое Nullable<T> (структура) или класс. Но разумной попыткой было бы:

 public bool IsValueType(object obj) {
    return obj != null amp;amp; obj.GetType().IsValueType;
}
  

но обратите внимание, что это неверно (и нефиксируемо) для пустых Nullable<T> файлов. Здесь становится бессмысленным беспокоиться о упаковке, поскольку мы уже упакованы.

Комментарии:

1. Есть ли способ обойти IsValueType свойство? Я использую .NET DNX, который не поддерживает это свойство.

2. typeof(ValueType).IsAssignableFrom(t) также не работает.

3. @Shimmy — в коде операционной системы — if (default(T) != null) должен сработать.

4. В DNX / .NET Core вы можете сделать это следующим образом typeof(your_type).GetTypeInfo().IsValueType .

Ответ №2:

Моим первым ответом было бы написать простой тест и выяснить это самостоятельно.

Моим вторым ответом (без какого-либо тестирования с моей стороны, конечно) был бы вариант 1. Это самая простая проверка. Второй метод включает в себя две отдельные проверки, в то время как третий предполагает создание экземпляра типа по умолчанию.

Вам также следует учитывать удобочитаемость. Фреймворк уже предоставляет вам возможность иметь в вашем коде следующее:

 if(someObj is ValueType)
{
    // Do some work
}
  

Зачем вообще беспокоиться о создании метода, который просто превратил бы приведенное выше утверждение в (при условии, что вы сделали свой метод статическим и позволили компилятору определить универсальный тип):

 if(IsValueType(someObj))
{
    // Do some work
}
  

Комментарии:

1. спасибо — и я не выступал за создание метода для его тестирования. Я просто написал это таким образом для ясности относительно того, о чем я спрашивал

2. (someObj is ValueType) кажется проблематичным для nulled Nullable<T> . Я не гуру IL, но я считаю, что здесь задействован бокс, и это не очень хорошо сочетается с этим сценарием.

3. Откуда вы знаете, is это самая простая проверка? Вы можете проверить, например, реализует ли объект интерфейс с ним, что не так «просто». Не знаю, как с этим справляется компилятор, но посмотрите, как реализованы IsAssignableFrom и вызываемый им ImplementInterface . Вы знаете что-то еще, или вы просто думаете, is что это быстрее, потому что это выглядит проще?

Ответ №3:

Определение структуры фактически определяет два типа: тип значения и тип класса, который является производным от System.ValueType . Если сделан запрос на создание переменной, параметра, поля или массива (совместно именуемого ‘место хранения’) типа, производного от System.ValueType , система вместо этого создаст место хранения, в котором будут храниться поля объекта, а не ссылка на объект, в котором эти поля отображаются. С другой стороны, если сделан запрос на создание экземпляра типа, производного от System.ValueType , система создаст экземпляр объекта класса, который является производным от System.ValueType.

Это можно продемонстрировать, создав структуру, которая реализует IValue:

интерфейс IValue {int value {get; set;}};
struct ValueStruct : IValue
{
 общедоступное значение int {get; set;}}; 
}

с помощью универсальной процедуры тестирования и кода для ее переноса:

тест статической пустоты<T>(T it) где T:IValue
{
 T дублировать = это; 
 it.value  = 1;
 дубликат.значение  = 10;
 Консоль.Строка записи (it.value.toString());
}
статический тест void ()
{
 ValueStruct v1 = новый ValueStruct();
 v1.значение = 9;
 IValue v2 = v1;
 Тест<ValueStruct>(v1); 
 Тест<ValueStruct>(v1); 
 Тест<IValue>(v1); 
 Тест<IValue>(v1); 
 Тест<IValue>(v2);
 Тест<IValue>(v2); 
}

Обратите внимание, что в любом случае вызов GetType для параметра, переданного в Test, приведет к получению ValueStruct , который сообщит о себе как о типе значения. Тем не менее, переданный элемент будет иметь «реальный» тип значения только при первых двух вызовах. При третьем и четвертом вызовах это действительно будет тип класса, о чем свидетельствует тот факт, что изменение на duplicate повлияет it . И при пятом и шестом вызовах изменение будет распространено обратно на версию v2, так что второй вызов «увидит» его.

Комментарии:

1. Обычно это можно описать как пакетирование… При третьем и четвертом вызовах вы помещаете в коробку сам вызов метода: когда T является интерфейсом, it это значение в штучной упаковке (иногда это поле можно оптимизировать), а duplicate — это просто ссылка на это поле. При пятом и шестом вызове вы передаете объекты, которые уже упакованы, потому что IValue v2 = v1; создали коробку. Поскольку вы дважды вводите одно и то же поле вместо создания двух отдельных полей, изменения, внесенные в поле при первом вызове, видны при втором вызове.

2. @AnorZaken: Термин «упаковка» действительно используется для описания процесса. У меня нет документации для . NET internals удобен, но он действительно описывает процесс в терминах наличия двух отдельных типов, и я думаю, что признание того, что коробочная структура является Object , в то время как распакованная структура таковой не является, более понятно, чем модель абстракции, используемая в C #. VB.NET добавляет в смесь некоторую собственную глупую логику. Если ссылка на тип интерфейса идентифицирует экземпляр типа значения в штучной упаковке, преобразование ссылки в type Object приведет к повторной упаковке экземпляра по какой-то причине, которую я не совсем понимаю.

Ответ №4:

 static class Metadata<T>
{
    static public readonly Type Type = typeof(T);
    static public readonly bool IsValueType = Metadata<T>.Type.IsValueType;
}

//fast test if T is ValueType
if(Metadata<T>.IsValueType) //only read static readonly field!
{
    //...
}
  

Комментарии:

1. Ограничение этого заключается в том, что он основан на typeof(T) , а не на тестировании входящего экземпляра. Обычно программист знает, является ли конкретное type значение или нет, общая потребность состоит в том, чтобы знать, является ли instance значение или нет. Рассмотрим параметр метода object obj . Этот ответ будет оценивать это на основе T=object объявленного типа параметра, а не типа среды выполнения конкретного экземпляра, поэтому вернет false , независимо от того, что obj такое. Но obj это может быть целое число в штучной упаковке или другой тип значения.

Ответ №5:

Существует два правила:

1-Все классы являются ссылочными типами, такими как Object и String, поэтому они поддерживаются классами .NET Framework.

2.Все структуры являются типами значений, такими как bool и char, даже если они содержат ссылочный элемент, поэтому они поддерживаются структурами .NET Framework.

Просто щелкните правой кнопкой мыши на любом типе и перейдите к Определению, если это класс, это означает, что это ссылочный тип, иначе, если это структура, это означает, что это тип значения 🙂

Комментарии:

1. для справочной информации, но что вы подразумеваете под «даже если он содержит ссылочный элемент»? Кроме того, я предполагаю, что инструкции GUI относятся к Visual Studio, правильно?

2. Правильно, это ссылка на Visual Studio, и я имею в виду, что у вас может быть структура, содержащая ссылку на объект …. и вы могли бы найти эти 2 предложения на msdn.microsoft.com/en-us/library/t63sy5hs.aspx «класс — это ссылочный тип. По этой причине ссылочные типы, такие как Object и String, поддерживаются классами .NET Framework. Обратите внимание, что каждый массив является ссылочным типом, даже если его члены являются типами значений.» «структура — это тип значения, даже если она содержит элементы ссылочного типа. По этой причине такие типы значений, как Char и Integer, реализованы структурами .NET Framework.»

3. Интересно, но вопрос в коде, а не в том, чтобы выяснить это в IDE.

4. Вопрос был об универсальном типе T, помните? «Перейти к определению» не поможет с T.

Ответ №6:

Вы можете использовать

 obj.GetType().IsValueType
  

Это использует отражение, но ясный способ вместо того, чтобы заботиться о распаковке упаковки.

Комментарии:

1. Этот ответ не отвечает на вопрос.