Що таке зловживання генериками?


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. Що таке зловживання генериками? І чи використовує загальний щоразу, коли параметр типу є зловживанням ?

2
Я збентежений. Ви знаєте заздалегідь назву властивості, але не тип об'єкта, тому ви використовуєте відображення для отримання значення?
MetaFight

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

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

2
ваш приклад дивно, але у мене є почуття , що це саме те , що багато бібліотек НІР зробити
Ewan

14
Не відповідь, але варто зазначити, що другий приклад менш гнучкий, ніж перший. Якщо функція є загальною, то ви знімаєте вашу здатність передавати у тип виконання; Генеріки повинні знати тип під час компіляції.
KChaloux

Відповіді:


87

Коли дженерики належним чином застосовані, вони видаляють код, а не просто переставляють його. Перш за все, код, який генеріки найкраще видаляти, - це набір тексту, відображення та динамічне введення тексту. Тому зловживання генеричними ресурсами може бути вільно визначено як створення загального коду без істотного скорочення набору тексту, рефлексії чи динамічного введення тексту порівняно з не-загальною реалізацією.

Що стосується вашого прикладу, я б очікувати , відповідне використання дженериків , щоб змінити object[]до T[]або подібне, і уникнути Typeабо typeвзагалі. Це може зажадати значного рефакторингу в іншому місці, але якщо використання дженериків доцільно в цьому випадку, воно закінчиться простішим в цілому, коли ви закінчите.


3
Це чудовий момент. Я визнаю, код, який змусив мене подумати про це, робив важкі для читання роздуми. Я буду бачити , якщо я можу змінити , object[]щоб T[].
SyntaxRules

3
Мені подобається характеристика видалити проти переставити.
мідь.хата

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

4
Відмінна відповідь. Додам, що в цьому конкретному випадку ОП може знадобитися перейти на інтерфейс замість генеричного. Схоже, що відображення використовується для доступу до певних властивостей об'єкта; інтерфейс набагато краще підходить, щоб дозволити компілятору визначити, що такі властивості існують насправді.
jpmc26

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

5

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

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

Отже, я б запропонував просто написати код, не використовуючи дженерики, поки не з’явиться потреба вводити їх (адже вам потрібна друга інстанція). Тоді, і лише тоді, настав час перефактурувати код, щоб використовувати дженерики. Будь-яке використання до цього моменту - це зловживання в моїх очах.


Здається, це здається занадто педантичним, тому нагадаю:
В програмуванні немає винятку без винятків. Це правило включало.


Цікаві думки. Однак незначне переформулювання ваших критеріїв може допомогти. Припустимо, OP потрібен новий "компонент", який відображає int на рядок і навпаки. Дуже очевидно, що він вирішує загальну потребу і може бути легко використаний у майбутньому, якщо зробити його загальним. Цей випадок впаде у ваше визначення зловживання генериками. Однак після того, як виявлена ​​сама загальна основна потреба, чи не варто було б вкладати незначні додаткові зусилля в дизайн, не зловживаючи цим?
Крістоф

@Christophe Це справді складне питання. Так, є деякі ситуації, коли дійсно краще ігнорувати моє правило (в програмуванні немає жодного правила без винятку!). Однак конкретний випадок перетворення рядків насправді є доволі задіяним: 1. Вам потрібно обробити підписані та неподписані цілі типи окремо. 2. Вам потрібно обробити плаваючі перетворення окремо від цілих конверсій. 3. Ви можете повторно використовувати реалізацію для найбільших доступних цілих типів для менших типів. Ви, можливо, захочете активно написати загальну обгортку, щоб зробити кастинг, але ядро ​​було б без дженериків.
cmaster

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

Написавши це запитання, я зайняв більше позиції "Напишіть це як загальне, якщо можливо, щоб зберегти можливе повторне використання". Я знав, що це було трохи екстремально. Тут освіжаюче бачити свою точку зору. Спасибі!
SyntaxRules

0

Код виглядає дивним. Але якщо ми це називаємо, виглядає приємніше вказати тип як загальний.

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

Особисто мені не подобаються ці спотворення, які роблять викликовий код гарним. наприклад, вся «вільна» річ та методи розширення.

Але ви повинні визнати, що у нього є популярне наступне. навіть мікрософт, наприклад, використовується в єдності

container.RegisterType<IInterface,Concrete>()

0

Мені здається, ви були на правильному шляху тут, але не закінчили роботу.

Ви розглядали можливість зміни object[]параметра на Func<T,object>[]наступний крок?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.