Не могли бы вы объяснить, что практическое использование для internal
ключевого слова в C #?
Я знаю, что internal
модификатор ограничивает доступ к текущей сборке, но когда и при каких обстоятельствах я должен его использовать?
Не могли бы вы объяснить, что практическое использование для internal
ключевого слова в C #?
Я знаю, что internal
модификатор ограничивает доступ к текущей сборке, но когда и при каких обстоятельствах я должен его использовать?
Ответы:
Служебные или вспомогательные классы / методы, к которым вы хотели бы получить доступ из многих других классов в той же сборке, но которые вы хотите гарантировать, что код в других сборках не будет доступен.
Из MSDN (через archive.org):
Широко используется внутренний доступ при разработке на основе компонентов, поскольку он позволяет группе компонентов взаимодействовать частным образом, не подвергаясь воздействию остальной части кода приложения. Например, платформа для построения графических пользовательских интерфейсов может предоставлять классы Control и Form, которые взаимодействуют, используя члены с внутренним доступом. Поскольку эти члены являются внутренними, они не подвергаются воздействию кода, использующего инфраструктуру.
Вы также можете использовать внутренний модификатор вместе с InternalsVisibleTo
атрибутом уровня сборки для создания «дружественных» сборок, которым предоставляется специальный доступ к внутренним классам целевой сборки.
Это может быть полезно для создания сборок модульного тестирования, которым затем разрешается вызывать внутренние элементы сборки, подлежащей проверке. Конечно, никаким другим сборкам не предоставляется этот уровень доступа, поэтому при выпуске системы инкапсуляция сохраняется.
Если Бобу нужен BigImportantClass, то Бобу нужно попросить людей, которые владеют проектом A, зарегистрироваться, чтобы гарантировать, что BigImportantClass будет написан для удовлетворения его потребностей, протестирован, чтобы убедиться, что он соответствует его потребностям, задокументирован как удовлетворяющий его потребностям, и что процесс будет введен в действие, чтобы гарантировать, что он никогда не будет изменен, чтобы больше не отвечать его потребностям.
Если класс является внутренним, ему не нужно проходить через этот процесс, что экономит бюджет для проекта А, который они могут потратить на другие вещи.
Дело не в том, что это усложняет жизнь Бобу. Это то, что он позволяет вам контролировать, какие дорогостоящие обещания Project A дает о функциях, сроке службы, совместимости и так далее.
Еще одна причина для использования внутренних - если вы запутываете свои двоичные файлы. Обфускатор знает, что безопасно зашифровать имя класса любых внутренних классов, в то время как имя открытых классов не может быть зашифровано, потому что это может сломать существующие ссылки.
Если вы пишете библиотеку DLL, которая инкапсулирует тонну сложных функций в простой публичный API, то для членов класса используется «внутренняя», которая не подлежит публичному раскрытию.
Сокрытие сложности (иначе говоря, инкапсуляция) является главной концепцией качественной разработки программного обеспечения.
Внутреннее ключевое слово интенсивно используется при создании оболочки над неуправляемым кодом.
Если у вас есть библиотека на основе C / C ++, для которой вы хотите использовать DllImport, вы можете импортировать эти функции как статические функции класса и сделать их внутренними, чтобы ваш пользователь имел доступ только к вашей оболочке, а не к исходному API, поэтому он не может возиться с чем угодно. Будучи статическими функциями, вы можете использовать их везде в сборке для нескольких нужных вам классов-оболочек.
Вы можете взглянуть на Mono.Cairo, это оболочка библиотеки Каира, которая использует этот подход.
Руководствуясь правилом «используйте как можно более строгий модификатор», я использую внутреннее везде, где мне нужен доступ, скажем, к методу из другого класса, пока мне явно не понадобится доступ к нему из другой сборки.
Поскольку интерфейс сборки обычно более узок, чем сумма интерфейсов его классов, я использую его достаточно много раз.
Я считаю, что внутреннее слишком много. Вы действительно не должны подвергать определенную функциональную неспособность только определенным классам, которые вы не сделали бы другим потребителям.
Это на мой взгляд нарушает интерфейс, нарушает абстракцию. Это не означает, что его никогда не следует использовать, но лучшим решением является рефакторинг в другой класс или использование по-другому, если это возможно. Однако это не всегда возможно.
Причины, по которым это может вызвать проблемы, заключаются в том, что другому разработчику может быть поручено создать другой класс в той же сборке, что и у вас. Наличие внутренних элементов уменьшает ясность абстракции и может вызвать проблемы при неправильном использовании. Это было бы той же проблемой, как если бы вы сделали это публично. Другой класс, который создается другим разработчиком, все еще является потребителем, как и любой внешний класс. Абстракция и инкапсуляция классов предназначены не только для защиты внешних классов, но и для любых классов.
Другая проблема заключается в том, что многие разработчики могут подумать, что им может понадобиться использовать его где-то в сборке, и пометить его как внутренний в любом случае, даже если им это и не нужно. Тогда другой разработчик может подумать, что его можно взять. Как правило, вы хотите пометить частное, пока у вас есть определительная потребность.
Но кое-что из этого может быть субъективным, и я не говорю, что его никогда не следует использовать. Просто используйте при необходимости.
Видел интересный на днях, может быть, неделю, в блоге, который я не могу вспомнить. В принципе, я не могу взять кредит на себя, но я подумал, что это может иметь какое-то полезное приложение.
Скажем, вы хотели, чтобы абстрактный класс был виден другой сборке, но вы не хотите, чтобы кто-то мог наследовать от него. Sealed не будет работать, потому что по какой-то причине он абстрактный, другие классы в этой сборке наследуют его. Private не будет работать, потому что вы можете объявить родительский класс где-то в другой сборке.
Пространство имен. Сборка { открытый абстрактный класс Parent { внутренняя абстрактная пустота SomeMethod (); } // Это работает просто отлично, поскольку находится в той же сборке. открытый класс ChildWithin: Parent { внутреннее переопределение void SomeMethod () { } } } Пространство имен Другое. Сборка { // Kaboom, потому что вы не можете переопределить внутренний метод открытый класс ChildOutside: Parent { } общедоступный тест { //Просто хорошо private Parent _parent; публичный тест () { // Все еще в порядке _parent = new ChildWithin (); } } }
Как вы можете видеть, он эффективно позволяет кому-либо использовать родительский класс без возможности наследования от него.
Этот пример содержит два файла: 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
Если у вас есть методы, классы и т. Д., Которые должны быть доступны в рамках текущей сборки и никогда за ее пределами.
Например, DAL может иметь ORM, но объекты не должны подвергаться бизнес-уровню, все взаимодействие должно осуществляться с помощью статических методов и передачи необходимых параметров.
Очень интересное использование внутреннего - с внутренним элементом, конечно, ограниченным только сборкой, в которой он объявлен, - в некоторой степени извлекает из него «дружественную» функциональность. Друг друг - это то, что видно только определенным другим сборкам за пределами сборки, в которой он объявлен. C # не имеет встроенной поддержки для друга, однако CLR делает.
Вы можете использовать InternalsVisibleToAttribute для объявления дружественной сборки, и все ссылки внутри дружественной сборки будут обрабатывать внутренние элементы вашей декларируемой сборки как общедоступные в области дружественной сборки. Проблема в том, что все внутренние элементы видны; вы не можете выбирать.
Хорошее использование InternalsVisibleTo состоит в том, чтобы выставлять различные внутренние элементы в сборку модульного тестирования, таким образом устраняя необходимость в сложных обходных решениях для тестирования этих элементов. Все внутренние элементы, видимые, не являются большой проблемой, однако такой подход довольно сильно портит интерфейсы вашего класса и потенциально может разрушить инкапсуляцию в объявленной сборке.
Как правило, есть два вида членов:
Снижение шума: чем меньше типов вы выставляете, тем проще ваша библиотека. Защита от несанкционированного вскрытия / безопасность - другое (хотя Reflection может победить это).
Внутренние классы позволяют вам ограничивать API вашей сборки. Это имеет свои преимущества, например упрощает понимание вашего API.
Кроме того, если в вашей сборке существует ошибка, вероятность исправления, вносящего критические изменения, меньше. Без внутренних классов вы должны были бы предположить, что изменение открытых членов любого класса было бы серьезным изменением. Что касается внутренних классов, вы можете предположить, что изменение их открытых элементов нарушает только внутренний API сборки (и любые сборки, на которые есть ссылка в атрибуте InternalsVisibleTo).
Мне нравится иметь инкапсуляцию на уровне класса и на уровне сборки. Есть некоторые, кто не согласен с этим, но приятно знать, что функциональность доступна.
У меня есть проект, который использует 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
много использовал , и это оказалось весьма полезным.
Бывают случаи, когда имеет смысл составлять члены классов internal
. Одним из примеров может быть, если вы хотите контролировать, как создаются экземпляры классов; допустим, вы предоставляете какую-то фабрику для создания экземпляров класса. Вы можете создать конструктор internal
так, чтобы фабрика (которая находится в той же сборке) могла создавать экземпляры класса, но код вне этой сборки не может.
Тем не менее, я не вижу смысла в создании классов или участников internal
без особых причин, так же мало, как это имеет смысл делать public
, или private
без особых причин.
единственное, что я когда-либо использовал ключевое слово internal - это код проверки лицензии в моем продукте ;-)
Одним из применений внутреннего ключевого слова является ограничение доступа к конкретным реализациям со стороны пользователя вашей сборки.
Если у вас есть фабрика или другое центральное место для конструирования объектов, пользователю вашей сборки нужно иметь дело только с открытым интерфейсом или абстрактным базовым классом.
Кроме того, внутренние конструкторы позволяют вам контролировать, где и когда создается экземпляр общедоступного класса.
Как насчет этого: обычно рекомендуется не предоставлять объект List внешним пользователям сборки, а предоставлять IEnumerable. Но намного проще использовать объект List внутри сборки, потому что вы получаете синтаксис массива и все другие методы List. Итак, у меня обычно есть внутреннее свойство, открывающее List для использования внутри сборки.
Комментарии приветствуются об этом подходе.
Имейте в виду, что любой класс, определенный как, public
будет автоматически отображаться в intellisense, когда кто-то смотрит на пространство имен вашего проекта. С точки зрения API важно показывать пользователям вашего проекта только те классы, которые они могут использовать. Используйте internal
ключевое слово, чтобы скрыть вещи, которые они не должны видеть.
Если ваш Big_Important_Class
проект Project A предназначен для использования вне вашего проекта, вы не должны отмечать его internal
.
Однако во многих проектах у вас часто будут классы, которые действительно предназначены только для использования внутри проекта. Например, у вас может быть класс, который содержит аргументы для вызова параметризованного потока. В этих случаях вы должны пометить их, как internal
если бы не по какой-либо другой причине, кроме как защитить себя от непреднамеренного изменения API в будущем.
private
Ключевое слово может быть использовано только на классы или структур, которые определены внутри другого класса или структуры. Класс, определенный в пространстве имен, может быть объявлен только public
или internal
.
Идея заключается в том, что при проектировании библиотеки только те классы, которые предназначены для использования извне (клиентами вашей библиотеки), должны быть общедоступными. Таким образом, вы можете скрыть классы, которые
и т.п.
Я полагаю, что если вы разрабатываете собственные решения, использование внутренних элементов не так важно, потому что обычно клиенты будут иметь постоянный контакт с вами и / или доступ к коду. Они довольно важны для разработчиков библиотек.
Когда у вас есть классы или методы, которые не совсем вписываются в объектно-ориентированную парадигму, которые делают опасные вещи, которые нужно вызывать из других классов и методов, находящихся под вашим контролем, и которые вы не хотите, чтобы кто-либо еще использовал ,
public class DangerousClass {
public void SafeMethod() { }
internal void UpdateGlobalStateInSomeBizarreWay() { }
}