Накладные расходы на использование структур в качестве оболочек для примитивов для проверки типов?

#c#

#c#

Вопрос:

Допустим, я хочу получить дополнительную проверку типов для работы с примитивами, которые семантически означают разные вещи:

 public struct Apple
{
    readonly int value;

    // Add constructor   operator overloads
}

public struct Orange
{
    readonly int value;

    // Add constructor   operator overloads
}
  

Дело в том, что мы не можем сравнить «яблоки с апельсинами», поэтому включение фактического значения int в структуру означает, что мы получаем проверку типа и некоторую дополнительную читаемость и документацию по коду.

Мой вопрос таков: каковы накладные расходы, связанные с выполнением этого, как с точки зрения памяти, так и скорости? Поскольку структуры являются типами значений, будут ли переменные, содержащие эти структуры, иметь размер 32 бита или больше? Как насчет накладных расходов на производительность при использовании этих структур вместо примитивов — есть ли большие накладные расходы из-за перегрузок оператора?

Есть какие-нибудь другие советы о разумности такого поступка?

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

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

2. В моем конкретном случае значения 0 — это нормально, но в целом полезно иметь в виду. То же самое можно сделать и для целых чисел, так что на самом деле это не сравнительный недостаток (по сравнению с примитивами), а ограничение, о котором следует помнить.

3. Проблему с конструктором по умолчанию обычно можно устранить, используя закрытые члены для хранения значений и свойств с логикой в получателях (а не установщиках), чтобы проверить default (T) и заменить его на желаемое значение. Логические флаги с «неправильным» значением по умолчанию могут быть уменьшены за счет того, что внутренний элемент хранит значение, противоположное значению интерфейса. Но я согласен, это проблема в C #, и ее действительно не должно быть.

4. Голосовал против, потому что я тоже хочу знать ответ, и за «не могу сравнить яблоки с апельсинами» 🙂 Вы когда-нибудь узнавали о накладных расходах на производительность? Я надеюсь, что накладные расходы — это весь синтаксический сахар, который испарится во время компиляции.

Ответ №1:

В памяти нет накладных расходов при использовании структуры, которую вы можете проверить с помощью Marshal.SizeOf() :

 struct testStruct
{
    public readonly int value;
}
..

int size = Marshal.SizeOf(typeof(testStruct)); //returns 4
  

Это также то же самое, что возвращается sizeof(testStruct) :

 unsafe
{
    int size = sizeof(testStruct); //returns 4
}
  

Согласно MSDN, разница между двумя методами определения размера заключается в:

Хотя вы можете использовать метод Marshal.SizeOf, значение, возвращаемое этим методом, не всегда совпадает со значением, возвращаемым sizeof. Marshal.SizeOf возвращает размер после того, как тип был маршализован, тогда как sizeof возвращает размер в том виде, в каком он был выделен общеязыковой средой выполнения, включая любое дополнение.

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

1. Это полезно знать. Я задавался вопросом, так ли это. И спасибо за советы по определению размера объектов. Я этого не знал.

Ответ №2:

Меня, вероятно, будут избегать за это, но вы могли бы сделать:

 using Apple = System.Int32;
using Orange = System.Int32;
  

Вы не сможете использовать его в файлах. И технически вы все еще можете сравнивать яблоки с апельсинами.

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

1. Помогает (немного) с удобочитаемостью, но не решает желание операционной системы проверять типы.