Что такое злоупотребление дженериками?


35

Просматривая некоторый код, я заметил возможность изменить его, чтобы использовать дженерики. (Запутанный) код выглядит так:

public void DoAllTheThings(Type typeOfTarget, object[] possibleTargets)
{
    var someProperty = typeOfTarget.GetProperty(possibleTargets[0]);
    ...
}

Этот код можно заменить на дженерики, например, так:

public void DoAllTheThings<T>(object[] possibleTargets[0])
{
    var someProperty = type(T).getProperty(possibleTargets[0]);
    ...
}

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

Мой вопрос состоит из двух частей:

  1. Есть ли какая-то польза от перехода на такие дженерики? (Производительность? Читабельность?)
  2. Что такое родовое насилие? И использует ли generic каждый раз, когда есть параметр типа, злоупотребление ?

2
Я запутался. Вам заранее известно имя свойства, но не тип объекта, поэтому вы используете отражение, чтобы получить значение?
MetaFight

3
@ MetaFight Я знаю, что это запутанный пример, фактический код будет слишком длинным и сложным для размещения здесь. Независимо от этого, цель моего вопроса состоит в том, чтобы определить, считается ли замена параметра Type универсальным признаком хорошей или плохой формой.
SyntaxRules

12
Я всегда считал, что это типичная причина существования дженериков (вкусный каламбур не предназначен). Зачем вводить обман самостоятельно, если вы можете использовать свою систему типов, чтобы сделать это для вас?
MetaFight

2
ваш пример странный, но у меня есть ощущение, что именно это делают многие библиотеки IoD
Ewan

14
Не ответ, но стоит отметить, что второй пример менее гибок, чем первый. Сделав функцию универсальной, вы лишаетесь возможности передавать тип во время выполнения; Дженерики должны знать тип во время компиляции.
KChaloux

Ответы:


87

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

Что касается вашего примера, я бы ожидал, что соответствующее использование обобщенных символов изменится на object[]a T[]или аналогичное, а также будет избегать Typeили typeвообще. Это может потребовать значительного рефакторинга в другом месте, но если в этом случае уместно использовать непатентованные средства, в целом все должно быть проще, когда вы закончите.


3
Это отличный момент. Я признаю, что код, который заставил меня задуматься об этом, выполнял некоторые трудно читаемые отражения. Я посмотрю, смогу ли я измениться object[]на T[].
SyntaxRules

3
Мне нравится описание удаления или перестановки.
медь. Что

13
Вы пропустили одно ключевое использование дженериков - чтобы уменьшить дублирование. (Хотя вы можете уменьшить дублирование, введя любой из других упомянутых вами грехов). Если это уменьшает дублирование без удаления типов типов, отражения или динамического ввода IMO, то все в порядке.
Майкл Андерсон

4
Отличный ответ. Я бы добавил, что в данном конкретном случае OP может потребоваться переключиться на интерфейс вместо универсальных. Похоже, что отражение используется для доступа к определенным свойствам объекта; интерфейс гораздо лучше подходит для того, чтобы компилятор мог определить, существуют ли такие свойства на самом деле.
jpmc26

4
@MichaelAnderson "удалить код вместо того, чтобы просто переставить его", включает в себя уменьшение дублирования
Caleth

5

Я бы использовал правило нонсенс: дженерики, как и все другие программные конструкции, существуют для решения проблемы . Если нет проблем с дженериками, их использование - это злоупотребление.

В конкретном случае универсальных шаблонов они в основном существуют для абстрагирования от конкретных типов, позволяя сворачивать реализации кода для различных типов в один универсальный шаблон (или как бы это ни назывался в вашем языке). Теперь предположим, что у вас есть код, который использует тип Foo. Вы можете заменить этот тип универсальным T, но если вам когда-либо нужен этот код для работы Foo, просто нет другого кода, с которым вы можете сложить его вместе. Таким образом, не существует никакой проблемы, которая может быть решена, поэтому косвенное добавление универсального кода просто уменьшит читабельность

Следовательно, я бы предложил просто написать код без использования обобщений, пока не возникнет необходимость их введения (потому что вам нужно второе создание). Тогда и только тогда наступает время для рефакторинга кода для использования обобщений. Любое использование до этого момента является злоупотреблением в моих глазах.


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


Интересные мысли. Однако небольшое изменение ваших критериев может помочь. Предположим, что OP нужен новый «компонент», который отображает int в строку и наоборот. Совершенно очевидно, что он решает общую потребность и может быть легко использован повторно в будущем, если будет сделан универсальным. Этот случай попадет в ваше определение злоупотребления дженериками. Однако, как только будет определена очень общая базовая потребность, не стоит ли вкладывать незначительные дополнительные усилия в разработку, если это не является злоупотреблением?
Кристоф

@ Christophe Это действительно сложный вопрос. Да, в некоторых ситуациях действительно лучше игнорировать мое правило (в программировании нет правил без исключения!). Тем не менее, конкретный случай преобразования строки на самом деле довольно сложен: 1. Вам нужно обрабатывать целочисленные типы со знаком и без знака отдельно. 2. Вы должны рассматривать преобразования с плавающей запятой отдельно от целочисленных преобразований. 3. Вы можете повторно использовать реализацию для наибольших доступных целочисленных типов для меньших типов. Вы можете заранее написать обобщенную обертку, чтобы выполнить приведение, но ядро ​​будет без обобщенных элементов.
Мастер

1
Я бы поспорил против проактивного написания универсального (предварительно использованного, а не повторного использования) как вероятного YAGNI. Когда вам это нужно, тогда рефакторинг.
Neil_UK

После написания этого вопроса я занял более жесткую позицию: «Если возможно, напишите его как общий, чтобы сохранить возможное повторное использование в будущем». Я знал, что это немного экстремально. Приятно видеть вашу точку зрения здесь. Благодарность!
SyntaxRules

0

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

https://stackoverflow.com/questions/10955579/passing-just-a-type-as-a-parameter-in-c-sharp

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

Но вы должны признать, что у него есть популярный последователь. даже Microsoft использует его в единстве, например

container.RegisterType<IInterface,Concrete>()

0

Мне кажется, ты был на правильном пути, но не закончил работу.

Рассматривали ли вы изменение object[]параметра Func<T,object>[]на следующий шаг?

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.