Практическое использование для «внутреннего» ключевого слова в C #


420

Не могли бы вы объяснить, что практическое использование для internalключевого слова в C #?

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

Ответы:


379

Служебные или вспомогательные классы / методы, к которым вы хотели бы получить доступ из многих других классов в той же сборке, но которые вы хотите гарантировать, что код в других сборках не будет доступен.

Из MSDN (через archive.org):

Широко используется внутренний доступ при разработке на основе компонентов, поскольку он позволяет группе компонентов взаимодействовать частным образом, не подвергаясь воздействию остальной части кода приложения. Например, платформа для построения графических пользовательских интерфейсов может предоставлять классы Control и Form, которые взаимодействуют, используя члены с внутренним доступом. Поскольку эти члены являются внутренними, они не подвергаются воздействию кода, использующего инфраструктуру.

Вы также можете использовать внутренний модификатор вместе с InternalsVisibleToатрибутом уровня сборки для создания «дружественных» сборок, которым предоставляется специальный доступ к внутренним классам целевой сборки.

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


8
Атрибут InternalsVisibleTo очень полезен. Спасибо.
erict

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

27
@GrimaceofDespair: Позвольте мне добавить здесь комментарий. Я лично вижу внутреннюю как очень полезную версию модификатора "public" в больших многопроектных решениях. Добавление этого модификатора к классу в моем подпроекте запрещает «случайное использование» этого класса другими подпроектами в решении и позволяет мне принудительно использовать мою библиотеку. Если позже мне понадобится рефакторинг, мне не придется спрашивать себя: «Подожди, но почему этот парень пошел и создал экземпляр класса Point, который мне был нужен только для моих собственных целей в этом конкретном вычислении»?
КТ.

4
Иными словами, если все будут полагаться на внутреннюю часть SmtpClient, и в один момент там будет обнаружена ошибка, у .NET возникнут серьезные проблемы, чтобы исправить эту ошибку, возможно, даже удерживая ее в течение длительного времени. Я помню, как однажды я прочитал забавную статью, в которой описана продолжительность, на которую разработчикам Windows пришлось идти, чтобы сохранить «совместимость с ошибками» со всеми более старыми программами, которые использовали различные внутренние функции и ошибки. Повторять эту ошибку с .NET не имеет смысла.
КТ.

13
Я думаю, что внутренний способ более полезен, чем публичный. Единственный раз, когда вы используете public, это когда вы явно говорите: «Здесь, пользователь - я предоставляю вам полезную функцию». Если вы пишете приложение, а не пользовательскую библиотеку, все должно быть внутренним, потому что вы не пытаетесь дать людям какие-либо функции для внешнего вызова. Если вы пишете пользовательскую библиотеку, то только те полезные функции, которые вы ожидаете предоставить пользователю, поддерживать, поддерживать и хранить до конца времени, должны быть общедоступными. В противном случае сделайте его внутренним.
Даррелл Планк

85

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

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

Дело не в том, что это усложняет жизнь Бобу. Это то, что он позволяет вам контролировать, какие дорогостоящие обещания Project A дает о функциях, сроке службы, совместимости и так далее.


5
Имеет смысл. Просто хотелось бы, чтобы у нас была возможность переопределять внутренних участников, потому что мы все здесь взрослые.
cwallenpoole

6
@EricLippert Не совсем то, что я имел в виду. Если я наследую скомпилированный код, в котором есть «внутренний» код, я застрял, верно? Нет никакого способа просто сказать компилятору: «Нет, другой разработчик солгал вам. Вы должны вместо этого доверять мне», если я не использую рефлексию.
cwallenpoole

11
@cwallenpoole: Если вы используете код, написанный лжецами, которые пишут код, который вам не нравится, прекратите использовать их код и напишите свой собственный код, который вам больше нравится.
Эрик Липперт

2
@EricLippert Это должно было быть языком в щеке, я не хотел обидеть. (Я просто пытался подтвердить, что я SOL в таких случаях).
cwallenpoole

5
@cwallenpoole: Я не обижаюсь ни в малейшей степени. Я предлагаю хороший совет, если вы окажетесь в этой неудачной ситуации.
Эрик Липперт

60

Еще одна причина для использования внутренних - если вы запутываете свои двоичные файлы. Обфускатор знает, что безопасно зашифровать имя класса любых внутренних классов, в то время как имя открытых классов не может быть зашифровано, потому что это может сломать существующие ссылки.


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

29
так что вы не закрываете дверь, когда спите, потому что никогда не слышали о воре, остановленном замком?
Серхио

4
Вот почему я шифрую имена классов и переменных в исходном коде. Возможно, вы сможете выяснить, что такое PumpStateComparator, но можете ли вы догадаться, что такое LpWj4mWE? #jobsecurity (я не делаю этого, и, пожалуйста, на самом деле не делайте этого, интернет-люди.)
Slothario

32

Если вы пишете библиотеку DLL, которая инкапсулирует тонну сложных функций в простой публичный API, то для членов класса используется «внутренняя», которая не подлежит публичному раскрытию.

Сокрытие сложности (иначе говоря, инкапсуляция) является главной концепцией качественной разработки программного обеспечения.


20

Внутреннее ключевое слово интенсивно используется при создании оболочки над неуправляемым кодом.

Если у вас есть библиотека на основе C / C ++, для которой вы хотите использовать DllImport, вы можете импортировать эти функции как статические функции класса и сделать их внутренними, чтобы ваш пользователь имел доступ только к вашей оболочке, а не к исходному API, поэтому он не может возиться с чем угодно. Будучи статическими функциями, вы можете использовать их везде в сборке для нескольких нужных вам классов-оболочек.

Вы можете взглянуть на Mono.Cairo, это оболочка библиотеки Каира, которая использует этот подход.


12

Руководствуясь правилом «используйте как можно более строгий модификатор», я использую внутреннее везде, где мне нужен доступ, скажем, к методу из другого класса, пока мне явно не понадобится доступ к нему из другой сборки.

Поскольку интерфейс сборки обычно более узок, чем сумма интерфейсов его классов, я использую его достаточно много раз.


3
Если вы «используете как можно более строгий модификатор», то почему не приватный?
Р. Мартиньо Фернандес

6
Поскольку private слишком строг, когда к свойствам / методам класса нужно обращаться за пределы класса, и в случаях, когда к ним нужно обращаться из другой сборки, bly встречается не так часто.
Илья Комахин

11

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

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

Причины, по которым это может вызвать проблемы, заключаются в том, что другому разработчику может быть поручено создать другой класс в той же сборке, что и у вас. Наличие внутренних элементов уменьшает ясность абстракции и может вызвать проблемы при неправильном использовании. Это было бы той же проблемой, как если бы вы сделали это публично. Другой класс, который создается другим разработчиком, все еще является потребителем, как и любой внешний класс. Абстракция и инкапсуляция классов предназначены не только для защиты внешних классов, но и для любых классов.

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

Но кое-что из этого может быть субъективным, и я не говорю, что его никогда не следует использовать. Просто используйте при необходимости.


Я часто строю доменные объекты, для которых у них есть члены, которые должны быть общедоступными для других доменных объектов в той же сборке. Однако эти же члены НЕ должны быть доступны клиентскому коду, который находится за пределами сборки. Для таких ситуаций очень подходит «внутренний».
Рок Энтони Джонсон

8

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

Скажем, вы хотели, чтобы абстрактный класс был виден другой сборке, но вы не хотите, чтобы кто-то мог наследовать от него. Sealed не будет работать, потому что по какой-то причине он абстрактный, другие классы в этой сборке наследуют его. Private не будет работать, потому что вы можете объявить родительский класс где-то в другой сборке.

Пространство имен. Сборка
{
  открытый абстрактный класс Parent
  {
    внутренняя абстрактная пустота SomeMethod ();
  }

  // Это работает просто отлично, поскольку находится в той же сборке.
  открытый класс ChildWithin: Parent
  {
    внутреннее переопределение void SomeMethod ()
    {
    }
  }
}

Пространство имен Другое. Сборка
{
  // Kaboom, потому что вы не можете переопределить внутренний метод
  открытый класс ChildOutside: Parent
  {
  }

  общедоступный тест 
  { 

    //Просто хорошо
    private Parent _parent;

    публичный тест ()
    {
      // Все еще в порядке
      _parent = new ChildWithin ();
    }
  }
}

Как вы можете видеть, он эффективно позволяет кому-либо использовать родительский класс без возможности наследования от него.


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 на public . Также измените уровень доступности члена 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

Снижение шума: чем меньше типов вы выставляете, тем проще ваша библиотека. Защита от несанкционированного вскрытия / безопасность - другое (хотя Reflection может победить это).


4

Внутренние классы позволяют вам ограничивать API вашей сборки. Это имеет свои преимущества, например упрощает понимание вашего API.

Кроме того, если в вашей сборке существует ошибка, вероятность исправления, вносящего критические изменения, меньше. Без внутренних классов вы должны были бы предположить, что изменение открытых членов любого класса было бы серьезным изменением. Что касается внутренних классов, вы можете предположить, что изменение их открытых элементов нарушает только внутренний API сборки (и любые сборки, на которые есть ссылка в атрибуте InternalsVisibleTo).

Мне нравится иметь инкапсуляцию на уровне класса и на уровне сборки. Есть некоторые, кто не согласен с этим, но приятно знать, что функциональность доступна.


2

У меня есть проект, который использует LINQ-to-SQL для данных конца. У меня есть два основных пространства имен: Biz и Data. Модель данных LINQ находится в Data и помечена как «внутренняя»; пространство имен 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

единственное, что я когда-либо использовал ключевое слово internal - это код проверки лицензии в моем продукте ;-)


1

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

Если у вас есть фабрика или другое центральное место для конструирования объектов, пользователю вашей сборки нужно иметь дело только с открытым интерфейсом или абстрактным базовым классом.

Кроме того, внутренние конструкторы позволяют вам контролировать, где и когда создается экземпляр общедоступного класса.


1

Как насчет этого: обычно рекомендуется не предоставлять объект List внешним пользователям сборки, а предоставлять IEnumerable. Но намного проще использовать объект List внутри сборки, потому что вы получаете синтаксис массива и все другие методы List. Итак, у меня обычно есть внутреннее свойство, открывающее List для использования внутри сборки.

Комментарии приветствуются об этом подходе.


@Samik - не могли бы вы уточнить, «как правило, рекомендуется не предоставлять объект List внешним пользователям сборки, а предоставлять IEnumerable». почему бесчисленное предпочтение?
Howiecamp

1
@Howiecamp: В основном потому, что IEnumerable предоставляет доступ к списку только для чтения, где, как будто вы открываете список напрямую, он может быть изменен клиентами (например, удалить элементы и т. Д.), Что может быть непреднамеренным.
Самик Р

Кроме того, предоставление списка или IList создает контракт, который вы всегда разрешаете (например) быстрый произвольный доступ к данным. Если когда-нибудь вы решите изменить свой метод на использование другой коллекции или вообще не использовать коллекцию (возвращение дохода), вам нужно будет либо создать список просто для возврата значения, либо разорвать контракт, изменив тип возврата метода. Использование менее определенного типа возврата дает вам гибкость.

1

Имейте в виду, что любой класс, определенный как, publicбудет автоматически отображаться в intellisense, когда кто-то смотрит на пространство имен вашего проекта. С точки зрения API важно показывать пользователям вашего проекта только те классы, которые они могут использовать. Используйте internalключевое слово, чтобы скрыть вещи, которые они не должны видеть.

Если ваш Big_Important_Classпроект Project A предназначен для использования вне вашего проекта, вы не должны отмечать его internal.

Однако во многих проектах у вас часто будут классы, которые действительно предназначены только для использования внутри проекта. Например, у вас может быть класс, который содержит аргументы для вызова параметризованного потока. В этих случаях вы должны пометить их, как internalесли бы не по какой-либо другой причине, кроме как защитить себя от непреднамеренного изменения API в будущем.


Так почему бы не использовать приват?
Заключенный НОЛЬ

2
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.