Навіщо використовувати публічний метод у внутрішньому класі?


250

В одному з наших проектів є багато коду, який виглядає приблизно так:

internal static class Extensions
{
    public static string AddFoo(this string s)
    {
        if (s == null)
        {
            return "Foo";
        }

        return $({s}Foo);
    }
}

Чи є якась чітка причина для цього, крім того, «що простіше опублікувати тип пізніше?»

Я підозрюю, що це має значення лише у дуже дивних крайових випадках (відображення у Silverlight) чи взагалі.


1
На мій досвід, це нормальна практика.
фог

2
@phoog, гаразд. але чому це нормальна практика? Може просто простіше, ніж змінити купу методів на внутрішні? Швидке виправлення -> позначте замість внутрішнього типу?
bopapa_1979

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

2
Відповідь Еріка Ліпперта дуже добре підводить моє мислення, а також підказує щось, що ховається в моєму мозку, намагаючись знайти вихід: неявна реалізація членів інтерфейсу повинна бути загальнодоступною.
фог

Хіба цей метод не дорівнює return s + "Foo";? +Оператор не дбає про нульові або порожніх рядках.
Міккель Р. Лунд

Відповіді:


419

ОНОВЛЕННЯ: Це питання було предметом мого блогу у вересні 2014 року . Дякую за чудове запитання!

Існує значна дискусія з цього питання навіть у самій команді упорядника.

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

Отже, з огляду на внутрішній клас, чи повинні його члени, до яких ви хочете отримати доступ до асамблеї, позначатися як громадські чи внутрішні?

Моя думка така: позначте таких членів як публічних.

Я використовую "public", щоб означати, що "цей член не є детальною інформацією про реалізацію". Захищений член - це деталь реалізації; в цьому є щось, що буде потрібно, щоб зробити похідну роботу класу. Внутрішній член - це деталь реалізації; щось інше внутрішнє для цієї асамблеї потребує члена, щоб правильно працювати. Громадський член каже, що "цей член представляє ключову документально підтверджену функціональність, що надається цим об'єктом".

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

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

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


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

10
+1, щоб сказати те, що я хотів сказати, але сформулював це набагато краще, ніж я міг (також для збільшення кута інтерфейсу, хоча зауважу, що це стосується лише неявних реалізацій членів).
фог

2
Важлива частина інтерфейсу - неможливо реалізувати метод інтерфейсу, використовуючи внутрішнє, це або публічне, або явне оголошення інтерфейсу. Тож слово public перевантажено двома значеннями.
Майкл Стум

8
З ідеологічної точки зору, ваш аргумент на користь publicдуже переконливий. Однак я вважаю, що "не завжди добре виходить ..." є особливо потужним. Втрата консистенції знецінює будь-які переваги "на перший погляд". Використовувати internalтаким чином означає свідомо будувати інтуїції, які іноді помиляються. Інтуїція здебільшого правильної - IMO - жахлива річ для програмування.
Брайан

3
Важлива цитата з вашого блогу: "Моя порада - обговорити це питання серед своєї команди, прийняти рішення, а потім дотримуватися цього".
bopapa_1979

17

Якщо клас internal, це не має значення , від доступності точки зору маркувати чи метод internalабо public. Однак все-таки добре використовувати тип, який ви б використовували, якби клас був public.

Хоча деякі кажуть, що це полегшує переходи від internalдо public. Він також виступає частиною опису методу. Internalметоди, як правило, вважаються небезпечними для безперешкодного доступу, тоді як publicметоди вважаються (здебільшого) вільною грою.

Використовуючи internalабо publicяк би ви хотіли в publicкласі, ви гарантуєте, що ви повідомляєте, який стиль доступу очікується, а також полегшує роботу, необхідну для створення класу publicв майбутньому.


Я схильний до обмеження всього, наскільки це можливо, створюючи хороший API і дозволяючи моєму споживачеві робити те, що я їх хочу робити. Я також з вами щодо того, щоб позначити члена, як я хотів би, якби клас був публічним. Більш конкретно, я б зазначив будь-якого учасника, лише якщо вважаю це завжди безпечним. Інакше хтось, хто пізніше маркує громадськість класу (це трапляється), може викрити небезпечних членів.
bopapa_1979

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

10

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


Дякую за відповідь. Я схильний наполегливо наполягати на тому, що "все" має значення при кодуванні, хоча :)
bopapa_1979

1
Ви прав, Ерік, це був трохи закинутий коментар. Сподіваюся, що решта моєї відповіді принесла користь. Я думаю, що відповідь Еріка Ліпперта - це те, що я намагався висловити, але він це пояснив набагато краще.
Ɖiamond ǤeezeƦ

8

Я підозрюю, що "простіше пізніше опублікувати тип?" є це.

Правила визначення значень означають, що метод буде видно лише як internal- тому насправді не має значення, позначаються методи publicчи internal.

Однією з можливих можливостей є те, що клас був загальнодоступним і пізніше був змінений, internalі розробник не покладався на те, щоб змінити всі модифікатори доступності методу.


1
+1 для прибиття очевидного сценарію "публічний клас став внутрішнім".
bopapa_1979

8

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


2

Це те саме, що публічний метод буде по-справжньому позначений як внутрішній, оскільки він знаходиться всередині внутрішнього класу, але він має перевагу (як ви здогадалися), якщо ви хочете позначати клас загальнодоступним, вам доведеться змінити менше коду.


2
вже є пов'язаний з цим питання: stackoverflow.com/questions/711361 / ...
Маріо Corchero

0

internalкаже, що до нього можна отримати доступ лише в межах однієї асамблеї. Інші класи в цій асамблеї можуть отримати доступ до internal publicчлена, але вони не зможуть отримати доступ до privateабо protectedчлена, internalчи ні.


Але якби методи були позначені internalзамість public, їх видимість не змінилася б. Я вважаю, що про це запитує ОП.
Oded

1
@zapthedingbat - публікація фактично правильна, але чи справді вона відповідає на питання?
Од

1
Правильно, питання ЧОМУ позначити їх саме так. Я розумію основи сфери застосування. Дякую за спробу, хоча!
bopapa_1979

0

Я фактично боровся з цим сьогодні. Досі я б сказав, що всі методи повинні бути позначені, internalякби клас був internalі вважав би що-небудь іншим просто поганим кодуванням або лінню, особливо в розвитку підприємства; однак мені довелося підклас publicкласу і перекрити один із його методів:

internal class SslStreamEx : System.Net.Security.SslStream
{
    public override void Close()
    {
        try
        {
            // Send close_notify manually
        }
        finally
        {
            base.Close();
        }
    }
}

Метод ОБОВ'ЯЗКОВО повинен бути, publicі я змусив мене думати, що насправді немає логічного сенсу встановлювати методи, internalякщо тільки вони насправді повинні бути, як сказав Ерік Ліпперт.

До сих пір я ніколи не переставав думати про це, я просто прийняв це, але після прочитання поста Еріка він насправді змусив мене задуматися і після багатьох роздумів це має багато сенсу.


0

Різниця є. У нашому проекті ми зробили багато класів внутрішніх, але ми робимо тестові одиниці в іншій збірці, і в нашій інформації про складання ми використовували InternalsVisibleTo, щоб дозволити збірці UnitTest викликати внутрішні класи. Я помітив, що у внутрішнього класу є внутрішній конструктор, ми не в змозі створити екземпляр, використовуючи Activator.CreateInstance в модульній тестовій збірці з певних причин. Але якщо ми змінимо конструктор на публічний, але клас все ще внутрішній, він прекрасно працює. Але я здогадуюсь це дуже рідкісний випадок (як Ерік сказав у оригінальній публікації: Роздум).


0

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

Подобається це:

public sealed class MyCurrentlySealedClass
{
    protected void MyCurretlyPrivateMethod()
    {
    }
}

Відповідно до "зразка", про який я згадував вище, це повинно бути ідеально добре. З цієї ж ідеї випливає. Він поводиться як privateметод, оскільки ти не можеш успадкувати клас. Але якщо ви видалите sealedобмеження, воно все-таки справедливо: в успадкованих класах можна побачити цей метод, що є абсолютно тим, чого я хотів досягти. Але ви отримуєте попередження: CS0628або CA1047. Вони обидва про те, щоб не оголосити protectedчленів у sealedкласі. Більше того, я знайшов повну згоду з приводу того, що це безглуздо: попередження "Захищений член у герметичному класі" (однокласний клас)

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


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