Почему типобезопасность является одним из основных преимуществ использования дженериков в C#?

Когда я изучал дженерики C#, в некоторых статьях упоминалось, что использование дженериков является типобезопасным во время выполнения, предотвращая использование данных, тип которых отличается от того, который используется в объявлении. Ссылка Я не понимаю, почему это должно быть проблемой, если тип неправильный, не должен ли он разбиться при сборке?

Мне любопытно, когда и как могла возникнуть такая проблема.

Вероятно, наиболее часто используемый общий тип — это List<T>. Сравните это с классом ArrayList, который он фактически заменил. Из этого сравнения должно быть ясно, что означает для универсальных типов безопасность типов и почему это хорошо.

jmcilhinney 27.11.2022 10:37
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
137
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Дженерики действительно являются типобезопасными во время компиляции. Я бы сказал, что артикль в предложении:

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

относится к неявному преимуществу устранения недопустимых исключений приведения во время выполнения.

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

void DoSomething<T>(T foo) where T : FooBase { }

Если я сейчас попытаюсь написать такой код:

var myBar = new Bar(); // Does not inherit from FooBase
DoSomething(myBar);

Тогда я получаю это:

ошибка CS0311: тип «Bar» не может использоваться в качестве параметра типа «T» в универсальном типе или методе «DoSomething (T)». Нет неявного преобразования ссылок из «Bar» в «FooBase».

И это происходит во время компиляции. Идеальный.

Возможно, вы также видели дженерики без каких-либо ограничений:

void DomSomething<T>(T foo);

Это немного отойдет от вашего первоначального вопроса, но можно спросить, в чем преимущество, скажем: DoSomething(object obj). И здесь у нас есть разница между типами значений и типами ссылок, а именно упаковка и распаковка происходят при использовании версии с object.

Таким образом, дженерики также могут иметь некоторые преимущества в производительности, наряду с безопасностью типов и возможностью повторного использования.

Почему бы просто не использовать void DoSomething(FooBase foo)? Generics здесь ничего не добавляет (исходя из имени, что FooBase не является интерфейсом)

Guru Stron 27.11.2022 10:32
Ответ принят как подходящий

Мне любопытно, когда и как могла возникнуть такая проблема.

В основном, при использовании типов, которые не поддерживают дженерики, но могут. Классическим примером, вероятно, является ArrayList — эквивалент List<T> до дженериков. ArrayList.Add просто принимает object, поэтому вы можете добавить в него что угодно, но обычно код предполагает только определенный тип. Так, например:

var people = new ArrayList();
people.Add(new Person("Jon"));

// ... later in the code
foreach (string name in people)
{
    Console.WriteLine(name);
}

Это приводит к сбою с ClassCastException, потому что список содержит ссылку Person, которая затем неявно преобразуется в ссылку string. Этого не произойдет с дженериками, где у вас будет либо List<Person>, либо List<string>, и вы сможете обнаруживать ошибки во время компиляции.

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

public interface IHaveId { int Id {get;}}

public T GetOrAddById<T>(IList<T> col, int id, T val) where T : class, IHaveId
{
    var item = col.FirstOrDefault(x => x.Id == id);
    if (item == null)
    {
        item = val;
        col.Add(item);
    }
    return item;
}

Теперь вы не можете передать ничего, что не реализует конкретный интерфейс.

До появления дженериков единственным способом получить обобщенную коллекцию было что-то вроде ArrayList (которое можно сравнить с List<object>), чтобы пользователь мог поместить в нее что угодно без какой-либо безопасности типов.

Другие вопросы по теме