Comments 26
Документация.
Ну а зачем может понадобиться? К примеру, один класс в нашей сборке реализует некий интерфейс. Другой класс наследует его, предоставляя чуть больше возможностей. Мы не хотим чтобы кто-то, кто будет использовать нашу сборку, наследовал один из наших классов и получил доступ к полю или методу, которое/ая используется для внутренних нужд, например, хранит некое состояние, изменив которое, дальнейшее поведение библиотеки может стать непредсказуемым.
using System;
namespace TestLibrary
{
internal class TestReflection
{
internal void SomeMethod()
{
// Do something
}
}
}
using System;
using System.Reflection;
namespace TestInheritance
{
class Program
{
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFrom("TestLibrary.dll");
Type type = assembly.GetType("TestLibrary.TestReflection");
ConstructorInfo typeCtor = type.GetConstructor(new Type[] { });
MethodInfo someMethod = type.GetMethod("SomeMethod", BindingFlags.Instance | BindingFlags.NonPublic);
object typeObject = typeCtor.Invoke(null);
someMethod.Invoke(typeObject, null);
}
}
}
И тогда internal метод internal класса прекрасно вызовется из другой сборки :)
Так что, делать internal методы в internal классах, мне кажется, излишне. Хотя я могу ошибаться :)
Таким же темпом и private вызываются.
1) Вы открытым текстом просите положить болт на инкапсуляцию, потому что вы знаете, что вы делаете.
2) Вы открытым текстом просите положить болт на инкапсуляцию, потому что вы думаете, что вы знаете, что вы делаете.
3) Вы пишите какой-то инструмент анализа кода, которому действительно нужно залезть в потроха ваших сборок. Ещё один случай использования рефлексии, о котором я почему-то не подумал.
Из этих трёх проблему представляет только второй вариант. И это является проблемой даже без рефлексии.
Что до internal, то я вижу дело так: рефлексия копает метаданные -> в метаданных из модификаторов доступа флаги Public/Non-public -> internal помечается как non-public. Отсюда вывод: надо взять Visual Studio и проверить. Но сто рублей на то, что от копания в internal это тоже защитит, я бы рискнул поставить.
В дополнение к предыдущему сообщению, только что наткнулся на вот такой вот атрибут (msdn)
[assembly: SuppressIldasm]
Не очень понятно, что оно делает, но вроде запрещает ildasm
дизассемблировать сборку, но не ограничивает использование reflection.
Есть подозрение, что это просто контракт — сборка принципиально не меняется, просто ildasm
говорит "извините, но нет".
Это слабо относится к теме инкапсуляции как таковой, но может, наверное, работать в сочетании с [DisablePrivateReflection]
и [InternalsVisibleTo]
.
Не исключаю, что я вас не правильно понял. Тогда, пожалуйста, прокомментируйте вот этот абзац:
Автор класса A решил, что ничего страшного не случится, если метод internal-класса пометить как public, чтобы компилятор не ныл, и чтобы не пришлось городить ещё кода. Интерфейс отмечен, как internal, класс, его реализующий, отмечен как internal, снаружи до метода, помеченного как public, вроде бы никак не добраться.На всякий случай, чтобы исключить возможное недопонимание, я не против модификатора internal. Сам его стараюсь применять, по тем же причинам, которые вы указали в начале статьи. Трюк с extension'ом — очень интересно, спасибо. Я имел в виду, что делать метод internal, когда класс и так уже internal, считаю лишним. И не понимаю, в чем не прав автор класса A.
И тут открывает дверь и тихонько крадётся рефлексия:
1) Это собьёт с толку того, кто читает код и вызовет ненужные вопросы.
2) Если модификатор доступа у класса изменится на public, то модификаторам метода лучше оставаться по умолчанию internal, пока программист не решит, что метод тоже можно открыть внешнему миру и явно не выскажет это пожелание, внеся изменение internal на public у метода в ближайшем коммите…
А явно указывать методу GetMethods и прочим, чтобы они гребли и не открытые члены тоже без острой на той надобности, не очень хорошо. Там уже в комментариях написали, что если вы так делаете, и у вас потом что-то сломалось, что ССЗБ.
Бесполезно с помощью модификаторов доступа пытаться защититься от того, что ваш класс "отрефлексируют". В метаданных сборки представлены все члены, даже самые приватные, иначе CLR не сможет с ними работать.
Модификаторы доступа — это не более чем контракт. Если вы объявили член публичным, то потребители вашей библиотеки вправе рассчитывать, что этот член не будет слишком уж часто меняться. А вот если они через рефлексию доставали internal классы, и после обновления библиотеки все перестало работать — так они ССЗБ.
Еще, модификатор класса "перекрывает" модификаторы его членов. Например, если класс (вложенный) объявлен как private, у него вполне легально может быть public конструктор, но использовать его можно будет только в пределах внешнего класса (опять же, не беря в расчет рефлексию).
Другие специикаторы доступа, даже тот же private, не защитят от рефлексии.
Подскажу ещё один метод пострелять по ногам: перезапись уже скомпилированной сборки средствами Cecil-а или Postsharp-а. Тоже можно много чего интересного сделать.
А причём тут инкапсуляция? Вся статья о сокрытии информации. Инкапсуляция это защита инвариантов и сокрытие информации лишь её часть. Учите матчасть.
internal class A : I
{
public void SomeMethod()
{
Если класс недоступен извне, то и методы его недоступны. Какая-то надуманная проблема…
Замечание про рефлексию неуместно, на то она и рефлексия, она может и приватные методы найти (ужас-ужас)
Если честно, у меня такие проблемы тоже были первый год или два работы. А потом я понял, что методы просто должны быть публичными. Модификатор internal
вообще перестал использовать. Нужно в приватном методе сделать хитрую операцию над входной строкой? Нет, мы не тестируем приватный метод, мы выносим хелпер-метод StringHelper.MagicalStringTrim
и тестируем его. Хотим потестировать еще что-то? Тоже берем и выносим.
В итоге тесты тестируют компактные типы, побитые на модули, и все довольны. По сути ваша цель в том, чтобы компоненты были по возможности чистыми функциями, тогда нет никакого смысла прятать их за internal, ведь пользователь ничего не может поломать. Ну а чтобы не засорять публичный неймспейс достаточно их класть в эти самые хелпер-сборки, которые не будут там никому мозолить глаза. Ну а приватные функции должны быть достаточно простыми, чтобы их тестировать надо было только интеграционно.
Таким образом путем рефакторинга огромный приватный метод разбивается на вызов нескольких публичных методов, которые уже протестированы, и агрегация результатов с которых очевидно корректна.
Я для себя выработал простое правило, если при работе с языком хочется странного, то стоит пересмотреть архитектуру и свой подход к решению проблемы. Например явная реализация нужна не для того, чтобы "спрятать" метод, а чтобы один класс мог реализовать методы разных интерфейсов, которые по стечению обстоятельств имеют одно имя и одну сигнатуру.
То есть, смотреть на internal через рефлексию невозможно технически
сюрприз-сюрприз :)
или я не понял сказанного автором, или автор не понял прочитанного в умных книжках…
UPD. Добавил уточняющий абзац в статью.
Возникает вопрос: что должны проверять юнит-тесты — что класс выполняет задачу правильно и sum(2,2) возвращает 4 или что задача А решена конкретно способом Б, а не В?
Если какая-то внутренняя логика требует отдельного тестирования, то может вынести ее (логику) в отдельный класс?
Если какая-то внутренняя логика требует отдельного тестирования, то может вынести ее (логику) в отдельный класс?
Да, так и нужно сделать. Но, скорее всего, этот класс будет internal, так как в контракт с внешним миром не входит. Речь не о том, чтобы тестировать потроха классов. Речь о том, чтобы тестировать классы и их, скажем так, не очень внутренние методы.
private protected
нужен для построения закрытых иерархий, но большой ценности в нём нет.
Например, у вас есть класс Foo
и наследники: Bar
, Baz
. Как сделать так, чтобы никто не смог добавить другого наследника? Можно сделать конструктор internal Foo()
, тогда из другой сборки его никто не вызовет.
Если сделать конструктор private protected Foo()
, его никто не сможет вызвать, кроме наследников в той же сборке. Если ваш Foo
абстрактный, то разницы нет вообще.
Инкапсуляция для настоящих самураев, или нюансы, связанные с ключевым словом internal в C#