Страницы

Поиск по вопросам

суббота, 30 ноября 2019 г.

C#. В чем разница между реализацией обобщенного класса через параметризацию типом и через тип object?

#c_sharp #generics #объекты #c_sharp_faq


Меня интересует этот вопрос с точки зрения производительности. Про то что object
требует приведение типов и из-за этого можно неявно допустить ошибку, я знаю.

UPD. Этот вопрос был задан на собеседовании. В качестве примера добавил 2 класса:

class SomeClass
{
    private T[] storage;
}

class SomeClass
{
    private object[] storage;
}

    


Ответы

Ответ 1



В том случае, если вы используете значимые типы, вы получаете дополнительные накладные расходы на упаковку и последующую распаковку. var numbers = new List(); numbers.Add(1); // упаковка №1 numbers.Add(2); // упаковка №2 int n1 = (int)numbers[0]; // распаковка №1 int n2 = (int)numbers[1]; // распаковка №2 Ну и как вы сами упомянули, такое использование не является типобезопасным. Обобщенные коллекции и были введены в .NET 2.0 как раз для того, чтобы избежать этих проблем. В более ранних версиях использовался ArrayList, который, по большому счету, является аналогом List. UPD В приведенном вами примере все то же самое: в случае, если в массив storage будут добавляться экземпляры значимых типов, будет происходить упаковка.

Ответ 2



Преимущества обобщенного кода, над не обобщенным: Безопасность типов. Когда обобщенный алгоритм применяется с конкретным типом, компилятор и CLR понимают это и следят за тем, чтобы в алгоритме использовались лишь объекты, совместимые с этим типом данных. Попытка использования несовместимого объекта приведет к ошибке на этапе компиляции или исключению во время выполнения. Объявив SomeType some = new SomeType() в вашей переменной storage можно будет хранить только объекты типа Stream или производные от него. Проверка будет происходить на этапе написания кода, т.е. сразу же выдаст предупреждение, в отличие от object - на этапе выполнения. Более простой и понятный код. Поскольку компилятор обеспечивает безопасность типов, в исходном тексте требуется меньше операция приведения типов, а такой код проще писать и сопровождать. Повышение производительности. До появления обобщений один из способов определения обобщенного алгоритма заключался в таком определении всех его членов, чтобы они «умели» работать с типом данных Object. Чтобы алгоритм работал с экземплярами значимого типа, перед вызовом членов алгоритма среда CLR должна была упаковать этот экземпляр. Упаковка требует выделения памяти в управляемой куче, что приводит к более частым процедурам уборки мусора, а это, в свою очередь, снижает производительность приложения. Поскольку обобщенный алгоритм можно создать для работы с конкретным значимым типом, экземпляры значимого типа могут передаваться по значению и CLR не нужно выполнять упаковку. Операции приведения типа также не нужны, поэтому CLR не нужно контролировать безопасность типов при их преобразовании, что также ускоряет работу кода. Для оценки производительности был использован не обобщенный ArrayList из библиотеки классов FCL и обобщенный List. Метод Main - вызов двух тестов public static void Main() { ValueTypePerfTest(); ReferenceTypePerfTest(); } Тестирование значимых типов: private static void ValueTypePerfTest() { const Int32 count = 10000000; using (new OperationTimer("List")) { List l = new List(); for (Int32 n = 0; n < count; n++) {          l.Add(n);                 // Без упаковки          Int32 x = l[n];           // Без распаковки }        l = null; // Для удаления в процессе уборки мусора } using (new OperationTimer("ArrayList of Int32")) { ArrayList a = new ArrayList(); for (Int32 n = 0; n < count; n++) {          a.Add(n);                  // Упаковка          Int32 x = (Int32) a[n];    // Распаковка }        a = null; // Для удаления в процессе уборки мусора } } Тестирование ссылочных типов: private static void ReferenceTypePerfTest() { const Int32 count = 10000000; using (new OperationTimer("List")) { List l = new List(); for (Int32 n = 0; n < count; n++) {        l.Add("X");                   // Копирование ссылки        String x = l[n];              // Копирование ссылки }      l = null; // Для удаления в процессе уборки мусора } using (new OperationTimer("ArrayList of String")) { ArrayList a = new ArrayList(); for (Int32 n = 0; n < count; n++) {          a.Add("X");                 // Копирование ссылки          String x = (String) a[n];   // Проверка преобразования        }                             // и копирование ссылки        a = null; // Для удаления в процессе уборки мусора } } Класс для оценки времени выполнения операций internal sealed class OperationTimer : IDisposable { private Int64 m_startTime; private String m_text; private Int32 m_collectionCount; public OperationTimer(String text) { PrepareForOperation(); m_text = text; m_collectionCount = GC.CollectionCount(0);      // Эта команда должна быть последней в этом методе      // для максимально точной оценки быстродействия m_startTime = Stopwatch.StartNew(); } public void Dispose() {      Console.WriteLine("{0} (GCs={1,3}) {2}", (m_stopwatch.Elapsed),         GC.CollectionCount(0), m_collectionCount, m_text); } private static void PrepareForOperation() { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } } Результаты тестирования 00:00:01.6246959 (GCs= 6) List 00:00:10.8555008 (GCs=390) ArrayList of Int32 00:00:02.5427847 (GCs= 4) List 00:00:02.7944831 (GCs= 7) ArrayList of String Вывод С типом Int32 обобщенный алгоритм List работает гораздо быстрее, чем не обобщенный алгоритм ArrayList. Более того, разница огромная: 1,6 секунды против 11 секунд, то есть в 7 раз быстрее! Кроме того, использование значимого типа (Int32) с алгоритмом ArrayList требует множества операций упаковки, и, как результат, 390 процедур уборки мусора, а в алгоритме List их всего 6. Результаты тестирования для ссылочного типа не столь впечатляющие: временные показатели и число операций уборки мусора здесь примерно одинаковы. Поэтому в данном случае у обобщенного алгоритма List реальных преимуществ нет. Тем не менее помните, что применение обобщенного алгоритма значительно упрощает код и контроль типов при компиляции. Источник: Jeffrey Richter "CLR via C#"

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

Отправить комментарий