Не могли бы вы объяснить, что практическое использование для internal
ключевого слова в С#?
Я знаю, что internal
модификатор ограничивает доступ к текущей сборке, но когда и при каких обстоятельствах я должен его использовать?
Не могли бы вы объяснить, что практическое использование для internal
ключевого слова в С#?
Я знаю, что internal
модификатор ограничивает доступ к текущей сборке, но когда и при каких обстоятельствах я должен его использовать?
Служебные или вспомогательные классы/методы, к которым вы хотели бы получить доступ из многих других классов в той же сборке, но которые вы хотите гарантировать, что код в других сборках не будет доступен.
Из MSDN (через archive.org):
Широко используется внутренний доступ при разработке на основе компонентов, поскольку он позволяет группе компонентов взаимодействовать частным образом, не подвергаясь воздействию остальной части кода приложения. Например, платформа для построения графических пользовательских интерфейсов может предоставлять классы Control и Form, которые взаимодействуют, используя члены с внутренним доступом. Поскольку эти члены являются внутренними, они не подвергаются воздействию кода, использующего инфраструктуру.
Вы также можете использовать внутренний модификатор вместе с атрибутом уровня сборки InternalsVisibleTo
для создания "дружественных" сборок, которым предоставлен специальный доступ к внутренним классам целевой сборки.
Это может быть полезно для создания сборок модульного тестирования, которым затем разрешается вызывать внутренние элементы сборки, подлежащей проверке. Конечно, никаким другим сборкам не предоставляется этот уровень доступа, поэтому при выпуске системы инкапсуляция сохраняется.
Если Бобу нужен BigImportantClass, тогда Боб должен получить людей, которые владеют проектом А, чтобы зарегистрироваться, чтобы гарантировать, что BigImportantClass будет написан для удовлетворения его потребностей, проверен, чтобы убедиться, что он соответствует его потребностям, документирован как отвечающий его потребностям, и что будет создан процесс, чтобы гарантировать, что он никогда не будет изменен, чтобы больше не отвечать его потребностям.
Если класс является внутренним, ему не нужно проходить этот процесс, что экономит бюджет для Project A, который они могут потратить на другие вещи.
Точка внутреннего - это не то, что жизнь Боба осложняет жизнь. Это позволяет вам контролировать, какой дорогой promises проект A делает о функциях, жизни, совместимости и т.д.
Другой причиной использования внутреннего является то, что вы запутываете свои двоичные файлы. Обфускатор знает, что он безопасен для скремблирования имени класса для любых внутренних классов, в то время как имя открытых классов не может быть скремблировано, поскольку это может нарушить существующие ссылки.
Если вы пишете DLL, которая инкапсулирует тонну сложных функций в простой публичный API, то для членов класса, которые не должны публично публиковаться, используется "внутренний".
Скрывая сложность (инкапсуляция a.k.a.) является основной концепцией качественной разработки программного обеспечения.
Внутреннее ключевое слово сильно используется, когда вы создаете оболочку по не управляемому коду.
Когда у вас есть библиотека на C/С++, которую вы хотите использовать DllImport, вы можете импортировать эти функции в качестве статических функций класса и сделать их внутренними, поэтому ваш пользователь имеет доступ только к вашей обертке, а не к исходному API, поэтому не может ни с чем портиться. Функции, являющиеся статическими, вы можете использовать их повсюду в сборке для нескольких классов-оболочек, которые вам нужны.
Вы можете взглянуть на Mono.Cairo, это обертка вокруг библиотеки cairo, которая использует этот подход.
Будучи управляемым с помощью "использования в качестве строгого модификатора, как вы можете", я использую внутреннюю информацию везде, где мне нужно получить доступ, скажем, к методу из другого класса, пока я явно не буду обращаться к нему с другой сборки.
Поскольку интерфейс сборки обычно более узкий, чем сумма его интерфейсов классов, есть много мест, в которых я его использую.
Я нахожу внутреннее, чтобы быть слишком злоупотребляющим. вы действительно не должны подвергать определенную функцию только определенным классам, которые вы не хотели бы использовать другим пользователям.
Это, на мой взгляд, нарушает интерфейс, ломает абстракцию. Это не означает, что он никогда не должен использоваться, но лучшим решением является рефакторинг для другого класса или его использование по-другому. Однако это может быть не всегда возможно.
Причинами, которые могут вызвать проблемы, является то, что другому разработчику может быть предъявлено обвинение в создании другого класса в той же самой сборке, что и ваша. Внутренность уменьшает ясность абстракции и может вызвать проблемы при неправильном использовании. Это была бы такая же проблема, как если бы вы сделали ее общедоступной. Другой класс, который строится другим разработчиком, по-прежнему остается потребителем, как и любой внешний класс. Абстракция класса и инкапсуляция не только для защиты/от внешних классов, но и для любых классов.
Другая проблема заключается в том, что многие разработчики подумают, что им может понадобиться использовать ее в другом месте сборки и пометить ее как внутреннюю в любом случае, хотя в то время они не нужны. Тогда другой разработчик может подумать, что он там для принятия. Обычно вы хотите пометить частный, пока у вас не будет определенной потребности.
Но некоторые из них могут быть субъективными, и я не говорю, что его никогда не следует использовать. Просто используйте, когда это необходимо.
Пила интересную на днях, может быть, неделю, в блоге, который я не помню. В принципе, я не могу взять на это кредит, но я подумал, что у него может быть полезное приложение.
Скажите, что вы хотели, чтобы абстрактный класс был замечен другой сборкой, но вы не хотите, чтобы кто-то мог унаследовать его. Запечатанное не будет работать, потому что оно абстрактно по какой-то причине, другие классы в этой сборке наследуют от него. Частный не будет работать, потому что вы можете захотеть объявить родительский класс где-нибудь в другой сборке.
namespace Base.Assembly { public abstract class Parent { internal abstract void SomeMethod(); } //This works just fine since it in the same assembly. public class ChildWithin : Parent { internal override void SomeMethod() { } } } namespace Another.Assembly { //Kaboom, because you can't override an internal method public class ChildOutside : Parent { } public class Test { //Just fine private Parent _parent; public Test() { //Still fine _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
Очень интересное использование внутреннего - с внутренним членом, которое, конечно, ограничено только сборкой, в которой оно объявлено, в какой-то степени получает функциональность "друга". Друг-член - это то, что видно только для некоторых других сборок вне собрания, в котором оно было объявлено. С# не имеет встроенной поддержки для друга, однако CLR делает.
Вы можете использовать InternalsVisibleToAttribute, чтобы объявить сборку друзей, и все ссылки из сборника друзей будут обрабатывать внутренних членов вашей объявляя сборку общедоступной в рамках сборки друга. Проблема в том, что все внутренние элементы видны; вы не можете выбрать.
Хорошее использование для InternalsVisibleTo заключается в том, чтобы выставлять различные внутренние элементы на сборку unit test, тем самым устраняя потребности в сложной работе рефлексии для тестирования этих членов. Все внутренние элементы, видимые, не являются проблемой, однако использование этого подхода очень сильно усложняет интерфейс вашего класса и может потенциально разрушить инкапсуляцию в объявляющей сборке.
Когда у вас есть методы, классы и т.д., которые должны быть доступны в рамках текущей сборки и никогда не за ее пределами.
Например, DAL может иметь ORM, но объекты не должны подвергаться бизнес-уровню, все взаимодействие должно выполняться статическими методами и передачей требуемых параметров.
Как правило, существуют два типа членов:
Уменьшение шума, тем меньше типов вы обнаружите более простую вашу библиотеку. Защита от несанкционированного доступа/безопасность - другое (хотя Отражение может победить против него).
Внутренние классы позволяют ограничить 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 имеют частный конструктор (для принудительного использования методов factory) и внутренний конструктор, который выглядит следующим образом:
internal Client(Data.Client client) {
this._client = client;
}
Это может использоваться любым бизнес-классом в библиотеке, но интерфейс интерфейса (front-end (UI) не имеет прямого доступа к модели данных, гарантируя, что бизнес-уровень всегда выступает в качестве посредника.
Это первый раз, когда я действительно использовал internal
, и это оказалось весьма полезным.
Бывают случаи, когда имеет смысл создавать члены классов internal
. Одним из примеров может быть, если вы хотите контролировать, как создаются классы; скажем, вы предоставляете своего рода factory для создания экземпляров класса. Вы можете сделать конструктор internal
, так что factory (который находится в той же сборке) может создавать экземпляры класса, но код вне этой сборки не может.
Однако я не вижу смысла создавать классы или члены internal
без особых причин, так же, как это имеет смысл сделать их public
или private
без особых причин.
единственное, что я когда-либо использовал внутреннее ключевое слово on, - это код проверки лицензии в моем продукте; -)
Одно использование внутреннего ключевого слова - ограничение доступа к конкретным реализациям от пользователя вашей сборки.
Если у вас есть factory или какое-либо другое центральное место для построения объектов, пользователь вашей сборки должен иметь дело только с открытым интерфейсом или абстрактным базовым классом.
Кроме того, внутренние конструкторы позволяют вам контролировать, где и когда создается публичный класс иначе.
Как насчет этого: обычно рекомендуется не подвергать объект List внешним пользователям сборки, а скорее подвергать IEnumerable. Но гораздо проще использовать объект List внутри сборки, потому что вы получаете синтаксис массива и все другие методы List. Таким образом, я обычно имею внутреннее свойство, отображающее список, который будет использоваться внутри сборки.
Комментарии приветствуются по поводу этого подхода.
Имейте в виду, что любой класс, определенный как public
, автоматически появится в intellisense, когда кто-то посмотрит на ваше пространство имен проектов. С точки зрения API важно показывать пользователям вашего проекта только те классы, которые они могут использовать. Используйте ключевое слово internal
, чтобы скрыть вещи, которые они не должны видеть.
Если ваш Big_Important_Class
для проекта A предназначен для использования вне вашего проекта, вы не должны отмечать его internal
.
Однако во многих проектах вы часто будете иметь классы, которые действительно предназначены только для использования внутри проекта. Например, у вас может быть класс, содержащий аргументы для параметризованного вызова потока. В этих случаях вы должны пометить их как internal
, если не по какой-либо другой причине, кроме как защитить себя от непреднамеренного изменения API по дороге.
Идея заключается в том, что при разработке библиотеки должны быть общедоступны только классы, предназначенные для использования извне (клиентами вашей библиотеки). Таким образом, вы можете скрыть классы, которые
и др.
Если вы разрабатываете внутренние решения, чем использование внутренних элементов, это не так важно, я думаю, потому что обычно клиенты будут иметь постоянный контакт с вами и/или доступ к коду. Однако они довольно критичны для разработчиков библиотек.
Когда у вас есть классы или методы, которые не соответствуют чисто объектно-ориентированной парадигме, которые делают опасные вещи, которые нужно вызывать из других классов и методов под вашим контролем, и которые вы не хотите позволять кто-нибудь еще использует.
public class DangerousClass {
public void SafeMethod() { }
internal void UpdateGlobalStateInSomeBizarreWay() { }
}