Когда я изучал дженерики C#, в некоторых статьях упоминалось, что использование дженериков является типобезопасным во время выполнения, предотвращая использование данных, тип которых отличается от того, который используется в объявлении. Ссылка Я не понимаю, почему это должно быть проблемой, если тип неправильный, не должен ли он разбиться при сборке?
Мне любопытно, когда и как могла возникнуть такая проблема.
Дженерики действительно являются типобезопасными во время компиляции. Я бы сказал, что артикль в предложении:
Клиентский код, использующий дженерики, является типобезопасным во время выполнения. предотвращение использования данных, тип которых отличается от используемого в декларации
относится к неявному преимуществу устранения недопустимых исключений приведения во время выполнения.
Обобщения обеспечивают безопасность типов во время компиляции, то есть вы не можете скомпилировать свой код, если нарушается универсальное ограничение. И это почти всегда предпочтительнее исключения во время выполнения.
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
не является интерфейсом)
Мне любопытно, когда и как могла возникнуть такая проблема.
В основном, при использовании типов, которые не поддерживают дженерики, но могут. Классическим примером, вероятно, является 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>
), чтобы пользователь мог поместить в нее что угодно без какой-либо безопасности типов.
Вероятно, наиболее часто используемый общий тип — это
List<T>
. Сравните это с классомArrayList
, который он фактически заменил. Из этого сравнения должно быть ясно, что означает для универсальных типов безопасность типов и почему это хорошо.