Зачем использовать публичный метод во внутреннем классе?

250

В одном из наших проектов много кода, который выглядит следующим образом:

internal static class Extensions
{
    public static string AddFoo(this string s)
    {
        if (s == null)
        {
            return "Foo";
        }

        return $({s}Foo);
    }
}

Есть ли какая-либо явная причина, чтобы сделать это, кроме как «легче сделать тип общедоступным позже?»

Я подозреваю, что это имеет значение только в очень странных крайних случаях (отражение в Silverlight) или вообще не имеет значения.

bopapa_1979
источник
1
По моему опыту, это нормальная практика.
Фог
2
@phoog, хорошо. но почему это нормальная практика? Может быть, проще, чем менять кучу методов на внутренние? Быстрое исправление -> пометить тип внутренний вместо?
bopapa_1979
это то, что я всегда предполагал. Однако у меня нет продуманного анализа, поэтому я не опубликовал ответ.
Фог
2
Ответ Эрика Липперта очень хорошо подводит итог моего мышления, а также затрагивает то, что скрывалось в моем мозгу, пытаясь найти выход: неявные реализации членов интерфейса должны быть публичными.
Фог
Разве этот метод не равен return s + "Foo";? +Оператор не заботится о нулевых или пустых строках.
Миккель Р. Лунд

Ответы:

419

ОБНОВЛЕНИЕ: Этот вопрос был темой моего блога в сентябре 2014 года . Спасибо за отличный вопрос!

По этому вопросу ведутся серьезные дебаты даже внутри самой команды разработчиков.

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

Итак, теперь, учитывая внутренний класс, должны ли его члены, к которым вы хотите получить доступ в сборке, быть помечены как открытые или внутренние?

Мое мнение таково: отметьте таких участников как общедоступных.

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

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

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

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

Эрик Липперт
источник
11
Мне нравится этот ответ ... он хорошо согласуется с моим отношением к написанию кода, который в максимально возможной степени самодокументируется, а область видимости - отличный способ сообщить о намерениях, особенно если другие члены вашей команды понимают ваше соглашение.
bopapa_1979
10
+1 за то, что я хотел сказать, но сформулировал это намного лучше, чем я мог (также за то, что поднял угол интерфейса, хотя я отмечаю, что это верно только для неявных реализаций членов).
Фог
2
Интерфейсная часть важна - невозможно реализовать метод интерфейса с использованием внутреннего, это либо публичное, либо явное объявление интерфейса. Таким образом, слово public перегружено двумя значениями.
Майкл Стум
8
С идеологической точки зрения ваш аргумент в пользу publicочень убедителен. Тем не менее, я считаю, что "не всегда хорошо получается", чтобы быть особенно мощным. Потеря последовательности обесценивает любые «краткие» преимущества. Использование internalтаким образом означает намеренное построение интуиции, которая иногда ошибочна. IMO - это правильная интуиция - ужасная вещь для программирования.
Брайан
3
Важная цитата из вашего блога: «Мой совет - обсудить проблему в вашей команде, принять решение, а затем придерживаться его».
bopapa_1979
17

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

Хотя некоторые говорят, что это облегчает переходы от internalк public. Он также служит частью описания метода. Internalметоды обычно считаются небезопасными для беспрепятственного доступа, в то время как publicметоды считаются (в основном) бесплатной игрой.

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

Guvante
источник
Я стремлюсь максимально ограничить все, создавая хороший API и позволяя своему предполагаемому потребителю делать то, что я намереваюсь сделать. Я также с вами отмечу участника так же, как если бы класс был публичным. Точнее говоря, я бы пометил любого участника публично, если бы я всегда считал его безопасным. В противном случае кто-то другой, отмечающий класс public позже (это случается), может выставить небезопасных членов.
bopapa_1979
@Eric: Если вы хотите отказаться от определения безопасности метода до тех пор, пока не будете готовы, это нормально, но я имел в виду только методы, которые считаются безопасными, и в этом случае нет никакого разоблачения.
Гуванте
10

Я часто отмечаю свои методы во внутренних классах как открытые, а не внутренние как: а) это на самом деле не имеет значения и б) я использую внутренний, чтобы указать, что метод является внутренним для намерения (есть некоторая причина, почему я не хочу раскрывать это Метод в публичном классе. Поэтому, если у меня есть внутренний метод, я действительно должен понять причину, по которой он является внутренним, прежде чем менять его на публичный, тогда как если я имею дело с публичным методом во внутреннем классе, мне действительно нужно подумать, почему Класс является внутренним, в отличие от того, почему каждый метод является внутренним.

Ɖiamond ǤeezeƦ
источник
Спасибо за ответ. Я склонен упорно настаивают на том, что «все» вопросы при кодировании, хотя :)
bopapa_1979
1
Вы правы, Эрик, это был небольшой комментарий. Надеюсь, что остальная часть моего ответа была полезна. Я думаю, что ответ Эрика Липперта - то, что я пытался выразить, но он объяснил это намного лучше.
Ɖiamond ǤeezeƦ
8

Я подозреваю, что "легче сделать тип позже публичным?" это.

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

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

Одед
источник
1
+1 за приближение очевидного сценария «публичный класс стал внутренним».
bopapa_1979
8

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

Тревор Пилли
источник
2

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

Марио Корчеро
источник
2
уже есть связанный вопрос: stackoverflow.com/questions/711361/…
Марио Корчеро
0

internalговорит, что к члену можно получить доступ только из одной сборки. Другие классы в этой сборке могут получить доступ к internal publicчлену, но не смогут получить доступ к члену privateили protectedчлену, internalили нет.

Райан П
источник
Но если бы internalвместо них были отмечены методы public, их видимость не изменилась бы. Я считаю, что это то, о чем спрашивает ОП.
Одед
1
@zapthedingbat - сообщение действительно корректно, но действительно ли оно отвечает на вопрос?
Одед
1
Правильно, вопрос в том, ПОЧЕМУ пометить их таким образом. Я понимаю основы охвата. Спасибо за попытку, хотя!
bopapa_1979
0

Я на самом деле боролся с этим сегодня. До сих пор я бы сказал, что все методы должны быть помечены, internalесли бы класс был internalи считал бы что-либо еще просто плохим кодированием или ленью, особенно в развитии предприятия; Тем не менее, я должен был подкласс класса publicи переопределить один из его методов:

internal class SslStreamEx : System.Net.Security.SslStream
{
    public override void Close()
    {
        try
        {
            // Send close_notify manually
        }
        finally
        {
            base.Close();
        }
    }
}

Метод ДОЛЖЕН быть, publicи это заставило меня задуматься о том, что на самом деле нет логического смысла в установке методов, internalесли только они не должны быть, как сказал Эрик Липперт.

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

Буря
источник
0

Там есть разница. В нашем проекте мы сделали много внутренних классов, но мы проводим модульное тестирование в другой сборке, и в нашей информации о сборке мы использовали InternalsVisibleTo, чтобы позволить сборке UnitTest вызывать внутренние классы. Я заметил, что если во внутреннем классе есть внутренний конструктор, мы не можем по какой-то причине создать экземпляр с использованием Activator.CreateInstance в сборке модульного теста. Но если мы изменим конструктор на public, но класс все еще будет внутренним, он будет работать нормально. Но я думаю, что это очень редкий случай (как сказал Эрик в оригинальном сообщении: «Отражение»).

Фарра Цзян
источник
0

Я думаю, что у меня есть дополнительное мнение по этому вопросу. Сначала мне было интересно, как имеет смысл объявить что-то публично во внутреннем классе. Затем я оказался здесь, читая, что было бы хорошо, если бы вы позже решили сменить класс на общедоступный. Правда. Итак, шаблон сформировался у меня в голове: если он не меняет текущее поведение, то будьте терпеливы и допускайте вещи, которые не имеют смысла (и не причиняют вреда) в текущем состоянии кода, но позже это будет, если вы изменить объявление класса.

Как это:

public sealed class MyCurrentlySealedClass
{
    protected void MyCurretlyPrivateMethod()
    {
    }
}

В соответствии с «шаблоном», который я упомянул выше, это должно быть совершенно нормально. Это следует той же идее. Он ведет себя как privateметод, так как вы не можете наследовать класс. Но если вы удалите sealedограничение, оно все равно будет действительным: унаследованные классы могут видеть этот метод, чего я и хотел достичь. Но вы получаете предупреждение: CS0628или CA1047. Они оба не объявляют protectedчленов в sealedклассе. Более того, я нашел полное согласие, о том, что это бессмысленно: предупреждение «Защищенный член в запечатанном классе» (одиночный класс)

Поэтому после того, как это предупреждение и дискуссия были связаны, я решил сделать все внутренним или менее внутренним, потому что оно больше соответствует такому мышлению, и мы не смешиваем разные «шаблоны».

Hix
источник
Я решительно предпочитаю делать это так (или, по крайней мере, по причинам), сказал Эрик Липперт. Его статья определенно стоит прочитать.
bopapa_1979