С#: System.Object vs Generics
Мне сложно понять, когда использовать Object (boxing/unboxing) против использования дженериков.
Например:
public class Stack
{
int position;
object[] data = new object[10];
public void Push (object o) { data[position++] = o; }
public object Pop() { return data[--position]; }
}
VS.
public class Stack<T>
{
int position;
T[] data = new T[100];
public void Push(T obj) {data[position++] = obj; }
public T Pop() { return data[--position]; }
}
Какой я должен использовать и при каких условиях? Кажется, что с помощью метода System.Object у меня могут быть объекты всех типов, которые в настоящее время живут в моем стеке. Так разве это не всегда предпочтительнее? Спасибо!
Ответы
Ответ 1
Всегда используйте дженерики! Использование объекта приводит к выполнению операций литья и боксу/распаковке типов значений. Из-за этих причин дженерики быстрее и элегантнее (без кастинга). И - основная причина - вы не получите InvalidCastException
с помощью дженериков.
Итак, генераторы быстрее, и ошибки отображаются во время компиляции. System.Object
означает исключения во время выполнения и литье, что в целом приводит к снижению производительности (иногда МНОГО ниже).
Ответ 2
Многие люди рекомендуют использовать дженерики, но похоже, что все они не понимают смысла. Это часто не касается производительности, связанной с примитивными типами бокса или кастингом, о том, как заставить компилятор работать для вас.
Если у меня есть список строк, я хочу, чтобы компилятор мне доказывал, что он всегда будет содержать список строк. Generics делает именно это - я указываю намерение, и компилятор доказывает это для меня.
В идеале я бы предпочел еще более богатую систему типов, где вы могли бы сказать, например, что тип (даже если это был ссылочный тип) не мог содержать нулевые значения, но С#, к сожалению, в настоящее время не предлагает этого.
Ответ 3
Вы будете почти всегда использовать дженерики.
Используется приложение System.Object как "общий тип":
- в старых приложениях pre-FX2, поскольку дженерики недоступны.
- когда вам нужно смешивать разные типы, как в объектах ASP.NET Session и Application.
Обратите внимание, что в первом примере (не общее) использование выглядит следующим образом:
Stack s = ...;
s.Push("Hello");
s.Push(1.23);
double d = (double) s.Pop();
string t = (string) s.Pop();
Вы действительно хотели бы избежать всего этого приведения типов (для удобства чтения, безопасности и производительности).
Ответ 4
Хотя бывают случаи, когда вы захотите использовать не-общую коллекцию (например, кэширование), у вас почти всегда есть коллекции однородных объектов, а не гетерогенных объектов. Для однородной коллекции, даже если она представляет собой набор вариантов базового типа или интерфейса, всегда лучше использовать дженерики. Это избавит вас от необходимости использовать результат в качестве реального типа, прежде чем вы сможете его использовать. Использование дженериков делает ваш код более эффективным и читабельным, потому что вы можете опустить код для выполнения трансляции.
Ответ 5
С типом object
, как вы говорите, вам нужно выполнить бокс и распаковку, которая очень утомительна. С дженериками нет необходимости в этом.
Кроме того, я предпочел бы более конкретно относиться к тому, с какими объектами может работать класс, а generics - отличная основа для этого. Зачем смешивать несвязанные типы данных в первую очередь? В вашем конкретном примере стека подчеркивается преимущество генериков по базовому типу данных object
.
// This stack should only contain integers and not strings or floats or bools
Stack<int> intStack = new Stack<int>();
intStack.Push(1);
Помните, что в generics вы можете указать интерфейсы, чтобы ваш класс мог взаимодействовать с объектами многих разных классов при условии, что все они реализуют один и тот же интерфейс.
Ответ 6
При возможности всегда предпочтительны общие характеристики.
Помимо производительности, Generics позволяет вам делать гарантии относительно типов объектов, с которыми вы работаете.
Основная причина, по которой это предпочтение отдается, заключается в том, что компилятор знает, какой тип объекта является объектом, и поэтому он может дать вам ошибки компиляции, которые вы обнаружите сразу, а не ошибки времени выполнения, которые могут произойти только в определенных сценариях, t.
Ответ 7
Все зависит от того, что вам нужно в долгосрочной перспективе.
В отличие от большинства ответов здесь я не буду говорить "всегда использовать дженерики", потому что иногда вам нужно смешивать кошек с огурцами.
Во что бы то ни стало, попробуйте использовать дженерики по всем причинам, уже приведенным в других ответах, например, если вам нужно комбинировать кошек и собак, создать базовый класс Mammal и иметь Stack<Mamal>
.
Но когда вам действительно нужно поддерживать все возможные типы, не бойтесь использовать объекты, они не кусаются, если вы не обращаетесь с ними плохо.:)
Ответ 8
Используйте generics, когда вы хотите, чтобы ваша структура обрабатывала один тип. Например, если вам нужен набор строк, вы хотите создать экземпляр строго типизированного списка строк:
List<string> myStrings = new List<string>();
Если вы хотите, чтобы он обрабатывал несколько типов, вы можете обойтись без генериков, но вы понесете небольшой удар производительности для операций бокса/распаковки.
Ответ 9
Дженерики не золотой молоток. В тех случаях, когда ваша деятельность не является общей, используйте старый добрый объект. Один из таких случаев - кеширование. Кеш, естественно, может содержать разные типы. Я недавно видел эту реализацию обертки кеша
void AddCacheItem<T>(string key, T item, int duration, ICacheItemExpiration expiration)
{
. . . . . . .
CacheManager.Add(cacheKey, item, .....
}
Вопрос: зачем, если CacheManager
берет объект?
Тогда был настоящий хаос в Get
public virtual T GetCacheItem<T>(string cacheKey)
{
return (T)CacheManager.GetData(cacheKey); // <-- problem code
}
Проблема выше в том, что тип значения потерпит крах.
Я исправил метод, добавив это
public T GetCacheItem<T>(string cacheKey) where T : class
Потому что мне нравится идея сделать это
var x = GetCacheItem<Person>("X")?
string name = x?.FullName;
Но я добавил новый метод, который позволит также принимать значения типов
public object GetCacheItem(string cacheKey)
Суть в том, что есть использование для object
, особенно при хранении разных типов в коллекции. Или когда у вас есть композиции, в которых могут существовать совершенно произвольные и не связанные объекты, когда вам нужно использовать их в зависимости от типа.
Ответ 10
Иногда вы просто не можете использовать дженерики для этой цели. Например:
public ConstructorName(object defaultObject = null) {}
Если вы измените параметр по умолчанию на generics, у него будет ошибка компиляции.