Практичне використання для "внутрішнього" ключового слова в C #


420

Чи можете ви поясніть, що таке практичне використання internalключового слова в C #?

Я знаю, що internalмодифікатор обмежує доступ до поточної збірки, але коли і за яких обставин я повинен її використовувати?

Відповіді:


379

Класи / методи утиліти чи помічники, до яких ви хочете отримати доступ з багатьох інших класів в межах однієї збірки, але до яких ви хочете переконатися, що код в інших збірках не може отримати доступ.

Від MSDN (через archive.org):

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

Ви також можете використовувати внутрішній модифікатор разом з InternalsVisibleToатрибутом рівня складання для створення "дружніх" збірок, яким надається спеціальний доступ до внутрішніх класів цільової збірки.

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


8
Атрибут InternalsVisibleTo - це чудова допомога. Дякую.
обмежитися

33
Я відчуваю, що з моменту, коли вам потрібен внутрішній, десь щось не так. ІМХО, треба мати можливість використовувати чистий ОО для вирішення всіх проблем. У цей самий момент я дивлюся приблизно на мільйонне використання внутрішнього джерела в .NET Framework, що перешкоджає використанню матеріалів, які вони використовують самі. Використовуючи внутрішні, вони створили моноліт, який є поверхнево модульним, але руйнується, коли ви намагаєтесь ізолювати речі. Крім того, безпека - це не аргумент, оскільки для порятунку є роздуми.
Гримаса відчаю

26
@GrimaceofDespair: Дозвольте додати тут діагностуючи коментар. Я особисто розглядаю внутрішню як дуже корисну версію модифікатора "public" у великих проектних рішеннях. Додавання цього модифікатора до класу в моєму підпроекті забороняє "випадкове використання" цього класу іншими підпроектами в рішенні і дозволяє мені змусити правильно використовувати мою бібліотеку. Якщо згодом мені доведеться зробити якийсь рефакторинг, мені не доведеться запитувати себе "чекай, але чому цей хлопець пішов і створив екземпляр класу Point, який мені потрібен був лише для моїх власних цілей у цьому конкретному обчисленні"?
КТ.

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

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

85

Якщо Боб потребує BigImportantClass, тоді Боб повинен змусити людей, які мають власний проект A, щоб підписатись, щоб гарантувати, що BigImportantClass буде написаний для задоволення його потреб, перевірений на те, щоб він відповідав його потребам, був задокументований як задоволення його потреб і що процес буде запроваджено для того, щоб він ніколи не був змінений, щоб більше не відповідати його потребам.

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

Сенс внутрішнього не в тому, що це ускладнює життя Бобу. Це те, що дозволяє контролювати, які дорогі обіцянки дає проект A щодо функцій, терміну експлуатації, сумісності тощо.


5
Має ідеальний сенс. Просто хотілося б, щоб у нас була можливість перекрити внутрішніх членів, тому що ми всі згідні тут на дорослих.
cwallenpoole

6
@EricLippert Не зовсім те, що я мав на увазі. Якщо я успадковую компільований код, у якому є "внутрішній", я застряг, правда? Немає можливості просто сказати компілятору "Ні, той інший розробник збрехав вам. Натомість вам слід довіряти мені", якщо я не використовую роздуми.
cwallenpoole

11
@cwallenpoole: Якщо ви використовуєте код, написаний брехунами, які пишуть код, який вам не подобається, то перестаньте використовувати їх код і напишіть власний код, який вам більше подобається.
Ерік Ліпперт

2
@EricLippert Це повинен був бути язиком у щоках, я не хотів ображати. (Я в основному просто намагався підтвердити, що я SOL в таких випадках).
cwallenpoole

5
@cwallenpoole: Я щонайменше не ображаюся. Я пропоную гарну пораду, якщо ви опинитеся в цій нещасній ситуації.
Ерік Ліпперт

60

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


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

28
щоб ви не зачинили двері, коли ви спите, бо ви ніколи не чули, щоб злодій зупинився замком?
Серхіо

4
Ось чому я перебираю імена класів та змінних у вихідному коді. Ви можете зрозуміти, що таке PumpStateComparator, але чи можете ви здогадатися, що таке LpWj4mWE? #jobsecurity (я цього не роблю, і, будь ласка, насправді цього не роблю, люди з Інтернету.)
Slothario

32

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

Приховування складності (також інкапсуляція) - головна концепція інженерії якості програмного забезпечення.


20

Внутрішнє ключове слово широко використовується, коли ви будуєте обгортку над некерованим кодом.

Якщо у вас є бібліотека на основі C / C ++, яку ви хочете DllImport, ви можете імпортувати ці функції як статичні функції класу, і зробити їх внутрішніми, тому ваш користувач має доступ лише до вашої обгортки, а не до оригінального API, тому він не може возитися ні з чим. Функції, які є статичними, ви можете використовувати їх скрізь у складі, для потрібних кількох класів обгортки.

Ви можете поглянути на Mono.Cairo, це обгортка навколо бібліотеки в Каїрі, яка використовує такий підхід.


12

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

Оскільки інтерфейс складання зазвичай вужчий, ніж сума його інтерфейсів класів, я використовую досить багато місць.


3
Якщо ви "використовуєте настільки суворий модифікатор, як ви можете", чому тоді не приватний?
Р. Мартіньо Фернандес

6
Оскільки приватні надто суворі, коли до класу властивостей / методів потрібно звертатися поза класом, а випадки, коли до них потрібно звертатися з іншого адесату, bly не так часто.
Ілля Комахін

11

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

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

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

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

Але дещо з цього може бути суб'єктивним, і я не кажу, що його ніколи не слід використовувати. Просто використовуйте, коли потрібно.


Я часто будую об’єкти доменів, для яких вони мають членів, які повинні бути загальнодоступними для інших об'єктів домену в межах однієї збірки. Однак ці ж члени НЕ повинні бути доступними для клієнтського коду, який знаходиться поза збіркою. У таких ситуаціях "внутрішня" дуже підходить.
Рок Ентоні Джонсон

8

Днями, можливо, тижня, побачив цікаве в блозі, якого я не пам'ятаю. В основному я не можу взяти на це кредит, але я подумав, що це може бути корисною програмою.

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

Простір імен Base.Assemble
{
  громадський абстрактний клас батьків
  {
    внутрішня абстрактна void SomeMethod ();
  }

  // Це працює чудово, оскільки знаходиться в одній збірці.
  громадський клас ChildWithin: Батько
  {
    внутрішня переопрацювання пустоти SomeMethod ()
    {
    }
  }
}

простір імен. Ще
{
  // Kaboom, тому що ви не можете змінити внутрішній метод
  громадський клас ChildOutside: батьківський
  {
  }

  Тест громадського класу 
  { 

    //Просто добре
    приватний батько _parent;

    громадський тест ()
    {
      // Ще добре
      _parent = новий ChildWithin ();
    }
  }
}

Як бачите, це ефективно дозволяє комусь користуватися класом Parent, не маючи можливості успадкувати від.


7

Цей приклад містить два файли: Assembly1.cs і Assembly2.cs. Перший файл містить внутрішній базовий клас, BaseClass. У другому файлі спроба інстанціювати BaseClass призведе до помилки.

// Assembly1.cs
// compile with: /target:library
internal class BaseClass 
{
   public static int intM = 0;
}

// Assembly1_a.cs
// compile with: /reference:Assembly1.dll
class TestAccess 
{
   static void Main()
   {  
      BaseClass myBase = new BaseClass();   // CS0122
   }
}

У цьому прикладі використовуйте ті самі файли, які ви використовували в прикладі 1, і змініть рівень доступності BaseClass на загальнодоступний . Змініть також рівень доступності IntM-члена на внутрішній . У цьому випадку ви можете створити екземпляр класу, але ви не можете отримати доступ до внутрішнього члена.

// Assembly2.cs
// compile with: /target:library
public class BaseClass 
{
   internal static int intM = 0;
}

// Assembly2_a.cs
// compile with: /reference:Assembly1.dll
public class TestAccess 
{
   static void Main() 
   {      
      BaseClass myBase = new BaseClass();   // Ok.
      BaseClass.intM = 444;    // CS0117
   }
}

джерело : http://msdn.microsoft.com/en-us/library/7c5ka91b(VS.80).aspx


6

Коли у вас є методи, класи тощо, які повинні бути доступні в межах поточної збірки і ніколи не знаходитися поза нею.

Наприклад, DAL може мати ORM, але об'єкти не повинні піддаватися діловому шару, вся взаємодія повинна здійснюватися статичними методами і передавати необхідні параметри.


6

Дуже цікаве використання внутрішнього - якщо внутрішній член, звичайно, обмежується лише тією збіркою, в якій він оголошений, - це отримати певну функцію "друга". Член-член - це те, що видно лише для деяких інших зборів поза межами асамблеї, в якій його заявлено. C # не має вбудованої підтримки для друга, однак CLR це робить.

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

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


5

Як правило, є два види членів:

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

4

Зменшення шуму, чим менше типів ви виставляєте, тим простіша бібліотека. Захист від несанкціонованого захисту / безпека - це інше (хоча Рефлексія може перемогти проти нього)


4

Внутрішні класи дозволяють обмежити API вашої збірки. Це має переваги, як зробити ваш API простішим для розуміння.

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

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


2

У мене є проект, який використовує LINQ-SQL для бек-енду даних. У мене є два основні простори імен: Biz та Data. Модель даних LINQ живе в даних і позначається як "внутрішня"; у просторі імен Biz є публічні класи, які обертаються навколо класів даних LINQ.

Так що є Data.Client, і Biz.Client; останні розкривають усі відповідні властивості об'єкта даних, наприклад:

private Data.Client _client;
public int Id { get { return _client.Id; } set { _client.Id = value; } }

Об'єкти Biz мають приватний конструктор (щоб змусити використовувати заводські методи) та внутрішній конструктор, який виглядає приблизно так:

internal Client(Data.Client client) {
    this._client = client;
}

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

Це перший раз, коли я насправді internalбагато використовував , і це виявляється досить корисним.


2

Бувають випадки, коли є сенс складати членів класів internal. Одним із прикладів може бути, якщо ви хочете контролювати, як класи інстанціюються; скажімо, ви надаєте якусь фабрику для створення екземплярів класу. Ви можете зробити конструктор internalтаким чином, що завод (який знаходиться в одній збірці) може створювати екземпляри класу, але код поза цією збіркою не може.

Однак я не бачу жодного сенсу робити заняття чи учасників internalбез конкретних причин, так само мало, як має сенс їх робити public, або privateбез конкретних причин.


1

єдине, що я коли-небудь використовував внутрішнє ключове слово, - це код перевірки ліцензії в моєму продукті ;-)


1

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

Якщо у вас є заводське або інше центральне місце для побудови об'єктів, користувачеві вашої збірки потрібно мати справу лише з відкритим інтерфейсом або абстрактним базовим класом.

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


1

Як щодо цього: зазвичай рекомендується не виставляти об'єкт List зовнішнім користувачам збірки, а відкривати IEnumerable. Але набагато простіше використовувати об'єкт List всередині збірки, оскільки ви отримуєте синтаксис масиву та всі інші методи List. Отже, у мене зазвичай є внутрішня властивість, що розкриває список, який буде використовуватися всередині збірки.

Коментарі про такий підхід вітаються.


@Samik - чи можете ви детальніше розглянути, "зазвичай рекомендується не виставляти об'єкт List зовнішнім користувачам збірки, а виставляти IEnumerable". Чому переважне безліч?
Howiecamp

1
@Howiecamp: Переважно тому, що IEnumerable надає доступ до списку лише для читання, де, якби ви відкриваєте список безпосередньо, він може бути змінений клієнтами (наприклад, видалення елементів тощо), що може бути ненавмисним.
Самік R

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

1

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

Якщо ваш Big_Important_Classпроект для проекту A призначений для використання поза вашим проектом, не слід його позначати internal.

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


То чому б не використовувати приватний замість цього?
В'язень ЗЕРО

1
privateКлючове слово може бути використано тільки на класи або структур, які визначені всередині іншого класу або структури. Клас, визначений у просторі імен, може бути оголошений лише publicабо internal.
Метт Девіс

1

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

  1. Можливо, змінитимуться у майбутніх випусках (якби вони були загальнодоступними, ви порушите код клієнта)
  2. Непридатні для клієнта і можуть викликати плутанину
  3. Небезпечні (тому неправильне використання може досить сильно зламати вашу бібліотеку)

тощо.

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


-7

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

public class DangerousClass {
    public void SafeMethod() { }
    internal void UpdateGlobalStateInSomeBizarreWay() { }
}

Що ви намагаєтеся сказати? Це не дуже зрозуміло. Принаймні не мені.
pqsk

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