Должно ли защищенное свойство в дочернем классе С# скрыть доступ к общедоступному свойству родителя?
У меня есть следующий код:
public class Parent
{
public string MyField { get; set; }
}
public class Child : Parent
{
protected new int MyField { get; set; }
}
Я пытаюсь получить доступ к этому с помощью:
static void Main(string[] args)
{
Child child = new Child();
child.MyField = "something";
}
Visual studio 2008 компилирует это без комментариев, но в Mono (2.4.2, Ubuntu) я получаю сообщение об ошибке
'HideTest.Child.MyField' is inaccessible due to its protection level (CS0122)
Является ли одна реализация или другая, более совместимая со стандартом здесь?
Изменить: Спасибо всем, кто указал на плохой дизайн. К сожалению, сторонняя библиотека и ее изменение значительно не практичны.
Ответы
Ответ 1
От ECMA-334 (спецификация С#) §10.7.1.2:
Объявление нового члена скрывает унаследованный элемент только в пределах области действия нового члена.
Это поведение можно увидеть, выполнив этот тест при внедрении Microsoft.
using System;
using NUnit.Framework;
namespace ScratchPad
{
[TestFixture]
public class Class1
{
[Test]
public void InheritanceHiding()
{
var b = new Base();
var d = new Derived();
var baseSomeProperty = b.SomeProperty;
var derivedSomeProperty = d.SomeProperty;
b.GetSomeProperty();
d.GetSomeProperty();
}
}
public class Base
{
public string SomeProperty
{
get
{
Console.WriteLine("Getting Base.SomeProperty");
return "Base.SomeProperty";
}
}
public string GetSomeProperty()
{
return SomeProperty;
}
}
public class Derived : Base
{
protected new int SomeProperty
{
get
{
Console.WriteLine("Getting Derived.SomeProperty");
return 3; //Determined by random roll of the dice.
}
}
public new int GetSomeProperty()
{
return SomeProperty;
}
}
}
Будет выводиться:
Getting Base.SomeProperty //(No Controversy)
Getting Base.SomeProperty //(Because you're calling from public scope and the new member is in protected scope, there is no hiding)
Getting Base.SomeProperty //(No Controversy)
Getting Derived.SomeProperty //(Now because you're calling from protected scope, you get the protected member).
Таким образом, свойство, к которому вы обращаетесь со своим Main()
, должно быть базовым классом (как в MS.NET), а не производным свойством (как в Mono), потому что новый производный член скрывает только ' старый "базовый элемент в защищенной области.
Mono делает что-то неправильно здесь в соответствии со спецификацией.
Ответ 2
Ответ Джейсона правильный, но он просит оправдания такого поведения. (А именно, что метод скрытия скрывается только в пределах метода скрытия.)
Существует ряд возможных оправданий. Один из них, в частности, заключается в том, что это еще один способ, с помощью которого дизайн С# смягчает проблему хрупкого базового класса.
FooCorp делает Foo.DLL:
public class Foo
{
public object Blah() { ... }
}
BarCorp делает Bar.DLL:
public class Bar : Foo
{
// stuff not having to do with Blah
}
ABCCorp делает ABC.EXE:
public class ABC
{
static void Main()
{
Console.WriteLine((new Bar()).Blah());
}
}
Теперь BarCorp говорит: "Вы знаете, что в нашем внутреннем коде мы можем гарантировать, что Blah только вернет строку благодаря нашим знаниям о нашей производной реализации. Давайте воспользуемся этим фактом в нашем внутреннем коде".
public class Bar : Foo
{
internal new string Blah()
{
object r = base.Blah();
Debug.Assert(r is string);
return (string)r;
}
}
ABCCorp поднимает новую версию Bar.DLL, которая содержит множество исправлений ошибок, которые блокируют их. Должны ли они сломаться, потому что у них есть призыв к Блаху, внутренний метод на Бар? Конечно нет. Это было бы ужасно. Это изменение - частная реализация, которая должна быть невидимой вне Bar.DLL.
Ответ 3
В целом, реализация С#.NET должна, вероятно, считаться "каноном". Из документации по новый модификатор:
Постоянное, поле, свойство или тип, введенный в класс или struct , скрывает всех членов базового класса с тем же именем.
... похоже, что реализация Mono правильнее с учетом этого определения. Он должен скрывать реализацию MyField
в классе Parent
, и поэтому он должен быть доступен только с помощью int MyField
подписи из класса Child
.
Ответ 4
Прелюдия: этот код сумасшедший. Если у вас действительно есть код в вашем приложении, как это, исправьте его сейчас. Либо сделайте их защищенными, либо открытыми!
Относительно ошибки: в CLR есть много действительно странных правил "краевого случая", чтобы иметь дело с такими вещами. Лучшее место для поиска такого рода вещей обычно блог Эрика Липперта.
Говоря, что, похоже, моно на самом деле делает более разумную вещь, на мой взгляд.
Во втором взгляде, С# один имеет больше смысла, когда вы учитываете вещи "за кулисами".
Свойства не являются "первоклассными" в MSIL. Свойство в С# или VB просто скомпилировано до метода get и set (компилятор также привязывает атрибут где-то для ведения бухгалтерии).
int MyField { get; set; }
будет фактически производить MSIL для двух методов:
void set_MyField(int value);
int get_MyField();
Теперь, учитывая, что ваш метод new
имеет другой тип, вы получите следующие два метода setter.
void set_MyField(int value);
void set_MyField(string value);
Когда вы вызываете x.MyField = "string"
, вы просто вызываете один из этих методов. Затем это сводится к сценарию нормальной перегрузки метода. Совершенно верно, что у него есть два метода с тем же именем, которые принимают разные параметры, поэтому компилятор просто выберет строку и продолжит ее веселье.
Так что да. С# один имеет смысл, если вы знаете, как работают внутренние компоненты, Mono имеет смысл, если вы этого не сделаете.
Какой из них "более правильный"? Спросите Эрика Липперта: -)
Ответ 5
Просто добавьте мои 2 цента). Это ошибка Mono, здесь является описанием.
Ответ 6
ИМХО, разница в том, что MS.NET распознает строку типа для MyField
и устанавливает значение свойства Parent
, а в Mono просто пытается получить доступ к MyField
в классе Child
.
Ответ 7
Вы делаете то, что доступно через базовый класс, недоступный через ребенка. Вы можете попробовать это, но на самом деле ничего не получится. Люди всегда могут это сделать:
Parent child = new Child();
и вызовите метод. Поэтому, если вы хотите, чтобы поле было скрыто, объявите новый и сохраните унаследованное одно общедоступное.