'Почему' и 'Где' Дженерики на самом деле используются?

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

Например,

Почему я не могу использовать string[] вместо List<string>?

Давайте полагать, что я объявляю универсальный класс, и он имеет универсальный параметр типа X.

T x;

Если я предоставляю метод для класса, который делает

x = x + 1;

Что означает на самом деле? Я не знаю что T на самом деле будет и я не знаю что x = x + 1 попытка работать на самом деле.

Если я не могу сделать свои собственные манипуляции в моих методах, как дженерики собираются помочь мне так или иначе?

Я уже изучил партию книжных ответов. Очень ценилось бы, может ли кто-либо обеспечить некоторое ясное понимание в этом.

С уважением, NLV

8
задан Venemo 26 December 2012 в 10:03
поделиться

10 ответов

Ваш вопрос отвечает сам на себя. Типы массивов сами по себе являются формой общей типизации. Если бы мы ввели систему общих типов в CLR v1.0, я готов поспорить, что не было бы специального типа массива, были бы просто Array наряду с List и так далее. Поскольку в первой версии CLR не было родовых типов, мы ввели в нее самый важный и очевидный родовой тип - тип массива. И в результате там есть целая куча специализированного кода только для работы с массивами - единственным общим типом, который CLR поддерживал в версии 1.0.

Поскольку массивы по сути являются родовым типом, ваш вопрос отвечает сам собой: причина появления родовых типов в целом - та же, что побудила создать шаблон типа массива. То есть: тип массива усиливает возможности своего базового типа определенным образом.

Int представляет число. Int[] представляет коллекцию чисел; мы усилили понятие числа до понятия коллекции чисел. Customer представляет клиента. Customer[] представляет понятие коллекции клиентов. Мы расширили понятие клиента до понятия коллекции клиентов. Отображение от типа T к типу T[] представляет собой абстрактное понятие общего усиления экземпляра типа до коллекции экземпляров типа.

Это же обоснование мотивирует все общие типы. Тип Nullable усиливает тип до понятия "эта вещь может быть экземпляром типа". Тип IComparable усиливает тип до понятия "экземпляр этого типа может быть упорядочен по отношению к другому экземпляру". И так далее. Каждый общий тип подобен шаблону массива: он представляет собой усиление типа в новый тип, который обеспечивает новые операции над этим типом.

Короче говоря: цель системы общих типов - позволить вам изобретать свои собственные усиления типов и манипулировать этими усилениями с помощью системы типов.

17
ответ дан 5 December 2019 в 05:19
поделиться

Мы используем дженерики, когда нам нужен полиморфизм "более высокого порядка", похожий на время компиляции утиная типизация . Когда мы говорим Foo , мы имеем в виду, что Foo <> зависит от некоторого свойства, которое может иметь любое возможное T , и либо данное T имеет это свойство, или код не компилируется.

Когда мы говорим x = x + 1 , мы подразумеваем, что T может добавить к нему 1 , и что возвращаемый тип - Т .

9
ответ дан 5 December 2019 в 05:19
поделиться

Поскольку, хотя string [] поддерживается (т. Е. Вычитается пользовательский тип массива), массив имеет: * Менее функционально, чем List или даже IList * является массивом, так что это не работает для других типов коллекций. А как насчет словарей?

Если я не могу делать свои собственные манипуляции с моими методами, как обобщенные шаблоны мне все равно помогут ?

Это немного похоже на вопрос: «Если я управляю вегетарианским рестораном, что хорошего, я могу покупать стейки у своего поставщика?».

В основном, когда ваши проекты усложняются, вы БУДЕТЕ писать свои собственные манипуляции в методах. Вы БУДЕТЕ делать все, где это имеет смысл.

0
ответ дан 5 December 2019 в 05:19
поделиться

Но почему на самом деле нам нужно сделать его универсальным?

Если хотите, вы также можете создать множество пользовательских классов: StringList , IntList , DoubleList и т. Д.Но суть дженериков в том, что вы можете определить свой класс один раз, и он работает для всех объектов.

И x = x + 1 , где x имеет общий тип T требует фактического вещественного типа для поддержки сложение с int естественным образом, или пусть оператор + перегружен с int в качестве второго типа.

3
ответ дан 5 December 2019 в 05:19
поделиться

На самом деле, я работаю над банкоматом на веб-сайте, где дженерики значительно упрощают мою работу. Мне нужно получить доступ к большому количеству информации из базы данных, поскольку весь сайт управляется базой данных, поэтому я действительно хотел иметь один класс для реализации функциональности базы данных. Этот класс наследуется большим количеством классов, которые знают, как обращаться со своими собственными данными.

Например, у меня есть класс Products, который занимается данными продуктов, и класс Themes, который занимается темами и т. Д.,. Что ж, всем этим классам нужен общий формат для чтения и записи. Итак, я создал класс записи, который этим занимается.

А теперь вот где начинается самое интересное. Я создал класс продукта, класс темы и т. Д.,. со строго типизированными членами, такими как Name, Manufacturer, ThemeId, однако класс Records не знает, как с ними бороться. Итак, экземпляр класса базы данных имеет тип, относящийся к конкретному классу, продукту, теме и т. Д. и класс Record также использует тип Generic, поэтому я могу написать такой код, как ...

Product.Name = "Cool Product";

, а затем сохранить его как ...

Products.InsertRecord(Product coolProduct);

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

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

3
ответ дан 5 December 2019 в 05:19
поделиться
public foobar<T>{
    public Action<string,T> SomeAction;
    public string SomeString;
    public foobar(Action<string,T> s,string m){
         SomeAction=s;
         SomeString=m;
    }


    public void Run(T foo){
         SomeAction(SomeString,foo);
    }
}

Без дженериков вам пришлось бы написать другой класс для каждой возможности или каких-то других сложных действий, чтобы получить желаемые результаты. Generics делает вещи немного более элегантными.

0
ответ дан 5 December 2019 в 05:19
поделиться

string [] дает нам массив фиксированного размера. Чтобы иметь список динамического размера, нам нужен класс. Поэтому, если нам нужен список строк, нам, возможно, придется создать класс с именем StringList.Для парного разряда DoubleList. И так далее. Но что насчет этих классов, уникальных для этих типов? Ничего такого. Поэтому вместо этого мы создаем общий класс List <>, который может принимать любой тип. Вместо того, чтобы снова и снова воссоздавать один и тот же класс для разных типов, дженерики избавляют нас от работы.

Что касается проблемы x = x + 1 , вы должны ожидать, что T имеет оператор + , который принимает int с правой стороны. Если этого не произойдет, это вызовет ошибку времени выполнения. Если это так, то этот код назначит x выходу оператора + с аргументами x и 1 .

Есть много других случаев, когда не выполняются уникальные операции, и класс / метод должен работать с более чем одним типом. Это также случаи, когда универсальные шаблоны важны и полезны.

Вам действительно решать, когда и где использовать их в своем коде. Если вы столкнетесь с проблемой, где они являются лучшим ответом, отлично. Используй их. В противном случае не делайте этого. Только не дисквалифицируйте их как решение, потому что они никогда не были ответом на вашу конкретную проблему.

0
ответ дан 5 December 2019 в 05:19
поделиться

Хорошим примером из моего собственного опыта работы было управление типами Linq-To-SQL. Одна основная операция, которую я должен выполнить при создании нового объекта базы данных:

  1. Проверить, существует ли запрошенный объект в локальном кэше.
  2. Иначе, проверьте, существует ли запрошенный объект в базе данных.
  3. В противном случае создайте экземпляр нового объекта с предоставленными параметрами.

В этом конкретном проекте существует около 100 различных типов объектов, поэтому написание этого кода для каждого из них было бы в лучшем случае обременительным, даже со сценарием, и что тогда нам делать, если нам нужно что-то изменить в методе?

Используя универсальные шаблоны вместе с ограничениями, я могу сделать что-то вроде этого:

public class SQLObjectManager<TEntity> where TEntity : class, new()
{
    public TEntity GetObject(int year, int stateID)
    {
        if( /* entity found in cache */)
            return GetFromCache(year,stateID);
        if( /* entity found in db */)
            return GetFromDB(year,stateID);
        TEntity entity = new TEntity();
        entity.Year = year;
        entity.StateID = stateID;
        return entity;
    }
}

В дополнение к преимуществу проверки типов во время компиляции у меня теперь есть одна функция, которую нужно реализовать и поддерживать вместо новой версии для каждой таблицы в моем база данных.

0
ответ дан 5 December 2019 в 05:19
поделиться

Обратите внимание, что если вы пришли из опыта работы с C ++, то универсальные шаблоны C # и шаблоны C ++ имеют схожие концепции. Хотя я считаю, что на самом деле они работают по-другому.

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

Предположим, вам нужен стек целых чисел; решением было бы придумать целочисленный класс стека. Затем для каждого требуемого типа данных (float, string и т. Д.) Вы должны написать отдельные версии класса; здесь вы поймете, что это приведет к дублированию кода.

Другим решением было бы написать стек, который был бы обобщен с использованием объекта в качестве типа элемента:

Как насчет того, чтобы следовать?

MyGenericStack<string> stringStack = new MyGenericStack<string>();
stringStack.Push("One");
stringStack.Push("Two");
stringStack.Push("Three");

MyGenericStack<int> intStack = new MyGenericStack<int>();
intStack.Push(1);
intStack.Push(2);
intStack.Push(3);

MyGenericStack<CustomObject> customObjectStack = new MyGenericStack<CustomObject>();
customObjectStack.Push(new CustomObject(1));
customObjectStack.Push(new CustomObject(2));
customObjectStack.Push(new CustomObject(3));

Ниже приводится общий класс

public class MyGenericStack<T>
{
    int position;
    T[] data = new T[10];
    public void Push(T obj) { data[position++] = obj; }
    public T Pop() { return data[--position]; }

    public void Show()
    {

        for (int i = 0; i < data.Length; i++)
        {
            //Console.WriteLine("Item: {0}", data[i]);
            //MessageBox.Show(String.Format("Item: {0}", data[i]));
        }
    }
}

Да, я знаю, теперь вы бы тоже влюбился в дженерики!

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

0
ответ дан 5 December 2019 в 05:19
поделиться

Для вашего первого вопроса вы могли бы выбрать List вместо string[], если бы хотели динамически добавлять строки в список - массив имеет фиксированный размер.

0
ответ дан 5 December 2019 в 05:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: