Как сделать свойство защищенным И внутренним в C#?
Вот мой сокращенный абстрактный класс:
abstract class Report {
protected internal abstract string[] Headers { get; protected set; }
}
Вот производный класс:
class OnlineStatusReport : Report {
static string[] headers = new string[] {
"Time",
"Message"
}
protected internal override string[] Headers {
get { return headers; }
protected set { headers = value; }
}
internal OnlineStatusReport() {
Headers = headers;
}
}
Идея в том, я хочу иметь возможность позвонить Report.Headers
из любой точки сборки, но разрешить ее установку только производным классам. Я пытался сделать Headers
только внутренний, но защищенный не считается более строгим, чем внутренний. Есть ли способ сделать заголовки внутренними, а их набор доступа защищенными и внутренними?
Мне кажется, что я неправильно использую модификаторы доступа, поэтому любая помощь в разработке была бы очень полезна.
7 ответов
Что плохого в том, чтобы обнародовать получателя? Если вы объявите собственность как
public string[] Headers { get; protected set; }
оно соответствует всем необходимым критериям: все члены сборки могут получить свойство, и только производные классы могут установить его. Конечно, классы вне сборки тоже могут получить свойство. Так?
Если вам действительно необходимо выставить свойство в вашей сборке, но не публично, другой способ сделать это - создать другое свойство:
protected string[] Headers { get; set; }
internal string[] I_Headers { get { return Headers; } }
Конечно, это уродливо украшать это имя I_
префикс. Но это довольно странный дизайн. Выполнение какого-либо искажения имени во внутреннем свойстве - это способ напомнить себе (или другим разработчикам), что свойство, которое они используют, неортодоксально. Кроме того, если позже вы решите, что смешанная доступность не является правильным решением вашей проблемы, вы будете знать, какие свойства нужно исправить.
Это не возможно в C#.
Для полноты картины это поддерживается в IL (модификатор доступа к семейству и сборке).
Я бы сохранил модификатор доступа как защищенный и имел бы внутренний вспомогательный метод.
protected override string[] Headers {
get { return headers; } // Note that get is protected
set { headers = value; }
}
internal SetHeadersInternal(string[] newHeaders)
{
headers = newHeaders;
}
Но так или иначе, это пахнет, как будто это должно быть как-то рефакторинг. Внутренний всегда то, что я бы использовал экономно, потому что это может привести к очень грязной архитектуре, где все каким-то образом использует все остальное в сборке, но, конечно, всегда есть исключения.
Вы можете использовать внутренний явно реализованный интерфейс:
internal interface IReport
{
string[] Headers { get; }
}
abstract class Report : IReport
{
protected abstract string[] Headers { get; protected set; }
string[] IReport.Headers
{
get { return Headers; }
}
}
class OnlineStatusReport : Report
{
static string[] headers = new string[] { "Time", "Message" };
protected internal override string[] Headers
{
get { return headers; }
protected set { headers = value; }
}
internal OnlineStatusReport()
{
Headers = headers;
}
}
Теперь вы получаете внутренний доступ к сборке, в которой определен IReport, что должно быть именно тем, что вам нужно.
Реализация интерфейсов в явном виде не является хорошо известной стратегией, но она решает множество проблем.
Начиная с C# 7.2 есть конструкция private protected
( ссылка). Он не позволяет читать с поля (таким образом, не выполняет в точности то, что намеревается OP), но стоит взять добычу.
CLR поддерживает концепцию защищенного И внутреннего (известную как доступ к семейству и сборке), и C# ДОЛЖЕН реализовать / раскрыть эту концепцию. C#, вероятно, должен позволять следующее:
internal string[] Header { get; protected set; }
В этом случае INTERSECT/AND должны использовать оба модификатора видимости для установщика свойств и позволять читать заголовки из любого места в пределах одной сборки, но устанавливать их только из производных классов в одной сборке.
Это распространенное убеждение, что вы не можете сделать некоторых членов одновременно защищенными и внутренними.
И это правда, что вы не можете сделать это в одной строке, как хотелось бы многим, включая меня, но с некоторой сообразительностью это выполнимо на 100%.
//Code below is 100% tested
/* FROM ProtectedAndInternal.dll */
namespace ProtectedAndInternal
{
public class MyServiceImplementationBase
{
protected static class RelevantStrings
{
internal static string AppName = "Kickin' Code";
internal static string AppAuthor = "Scott Youngblut";
}
}
public class MyServiceImplementation : MyServiceImplementationBase
{
public void PrintProperties()
{
// WORKS PERFECTLY BECAUSE SAME ASSEMBLY!
Console.WriteLine(RelevantStrings.AppAuthor);
}
}
public class NotMyServiceImplementation
{
public void PrintProperties()
{
// FAILS - NOT THE CORRECT INHERITANCE CHAIN
// Error CS0122: 'ProtectedAndInternal.MyServiceImplementationBase.Relevant' is inaccessible due to its protection level
// Console.WriteLine(MyServiceImplementationBase.RelevantStrings.AppAuthor);
}
}
}
/* From AlternateAssemblyService.dll which references ProtectedAndInternal.dll */
namespace AlternateAssemblyService
{
public class MyServiceImplementation : MyServiceImplementationBase
{
public void PrintProperties()
{
// FAILS - NOT THE CORRECT ASSEMBLY
// Error CS0117: 'ProtectedAndInternal.MyServiceImplementationBase.RelevantStrings' does not contain a definition for 'AppAuthor'
// Console.WriteLine(RelevantStrings.AppAuthor);
}
}
}
Не совсем элегантное, но работоспособное решение, реализуйте его явно:
internal bool _allowSetHeader = false;
protected void SetHeader(string[] newValue)
{
if (_allowSetHeader)
{
headers = newValue;
}
}
SetHeader может быть доступен только производному классу, и он ничего не будет делать, если для _allowSetHeader не установлено значение true, что может быть выполнено только внутренним классом...