Поделиться через


Общие сведения о универсальных типах

Разработчики используют универсальные шаблоны все время в .NET, будь то неявно или явно. Когда вы используете LINQ в .NET, вы когда-либо заметили, что вы работаете с IEnumerable<T>? Или если вы когда-либо видели в Интернете пример универсального репозитория для взаимодействия с базами данных с помощью Entity Framework, вы видели, что большинство методов возвращают IQueryable<T>? Возможно, вы задались вопросом, что T находится в этих примерах и почему он там.

Впервые появившись в .NET Framework 2.0, универсальные шаблоны по сути являются "шаблоном кода", который позволяет разработчикам определять структуры данных, безопасные для типов , без фиксации фактического типа данных. Например, List<T> это универсальная коллекция, которую можно объявить и использовать с любым типом, например List<int>List<string>, или List<Person>.

Чтобы понять, почему универсальные шаблоны полезны, давайте рассмотрим конкретный класс до и после добавления универсальных шаблонов: ArrayList В .NET Framework 1.0 элементы ArrayList были типа Object. Любой элемент, добавленный в коллекцию, автоматически преобразуется в элемент Object. То же самое произойдет при чтении элементов из списка. Этот процесс называется упаковкой и распаковкой, что влияет на производительность. Кроме вопросов производительности, однако, нет способа определить тип данных в списке во время компиляции, что делает код уязвимым. Универсальные шаблоны решают эту проблему, определив тип данных каждого экземпляра списка. Например, можно добавлять только целые числа в List<int> и только людей в List<Person>.

Универсальные шаблоны также доступны во время выполнения. Среда выполнения знает, какой тип структуры данных вы используете, и может хранить ее в памяти более эффективно.

В следующем примере показана небольшая программа, которая иллюстрирует эффективность знания типа структуры данных во время выполнения:

  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Diagnostics;

  namespace GenericsExample {
    class Program {
      static void Main(string[] args) {
        //generic list
        List<int> ListGeneric = new List<int> { 5, 9, 1, 4 };
        //non-generic list
        ArrayList ListNonGeneric = new ArrayList { 5, 9, 1, 4 };
        // timer for generic list sort
        Stopwatch s = Stopwatch.StartNew();
        ListGeneric.Sort();
        s.Stop();
        Console.WriteLine($"Generic Sort: {ListGeneric}  \n Time taken: {s.Elapsed.TotalMilliseconds}ms");

        //timer for non-generic list sort
        Stopwatch s2 = Stopwatch.StartNew();
        ListNonGeneric.Sort();
        s2.Stop();
        Console.WriteLine($"Non-Generic Sort: {ListNonGeneric}  \n Time taken: {s2.Elapsed.TotalMilliseconds}ms");
        Console.ReadLine();
      }
    }
  }

Эта программа создает выходные данные, аналогичные следующему:

Generic Sort: System.Collections.Generic.List`1[System.Int32]
 Time taken: 0.0034ms
Non-Generic Sort: System.Collections.ArrayList
 Time taken: 0.2592ms

Первое, что вы можете заметить здесь, заключается в том, что сортировка универсального списка значительно быстрее, чем сортировка не универсального списка. Вы также можете заметить, что тип универсального списка отличается ([System.Int32]), в то время как тип для не универсального списка обобщен. Так как среда выполнения знает, что универсальный List<int> имеет тип Int32, она может хранить элементы списка в базовом массиве целых чисел в памяти, а неуниверсальный ArrayList должен привести каждый элемент списка к объекту. Как показано в этом примере, дополнительные приведения данных занимают время и замедляют сортировку списка.

Дополнительное преимущество среды выполнения, знающей тип вашего обобщенного типа, — это улучшенный процесс отладки. При отладке универсального элемента в C#вы знаете, какой тип каждого элемента находится в структуре данных. Без универсальных шаблонов не было бы понятия о типе каждого элемента.

См. также