Инициализатор поля, обращающийся к `this`: недопустимый в С#, действительный в Java?
Во-первых, введение:
Этот код:
class C
{
int i = 5;
byte[] s = new byte[i];
}
не удается скомпилировать следующую ошибку:
Инициализатор поля не может ссылаться на нестатическое поле, метод или свойство `C.i '
Resharper говорит нечто подобное: не удается получить доступ к нестатическому полю i
в статическом контексте.
Это соответствует тому, что С# spec говорит - что инициализатор поля не может получить доступ к экземпляру, создаваемому в данный момент (this
) или, в добавлении, любое из полей экземпляра:
Инициализатор переменных для поля экземпляра не может ссылаться на созданный экземпляр. Таким образом, это ошибка времени компиляции для ссылки это в переменном инициализаторе, поскольку это ошибка времени компиляции для инициализатор переменной для ссылки на любой экземпляр через простое имя.
Однако это отлично работает в Java:
class C {
int i = 5;
byte s[] = new byte[i]; //no errors here
}
Еще со мной? Хорошо, вот вопрос. Err, вопросы.
В гипотетическом мире, где это было бы справедливо в С#, мне интересно: возможно ли даже ? Если это так, , какие будут плюсы и минусы, которые он добавит в таблицу?
Кроме того, поскольку он действительно поддерживается Java, делать те же самые плюсы и минусы для для Java? Или существует фундаментальная разница в том, как инициализаторы типов работают на двух языках?
Ответы
Ответ 1
Короче говоря, возможность доступа к получателю перед запуском тела конструктора является особенностью предельных преимуществ, которая облегчает запись багги-программ. Поэтому разработчики языка С# полностью отключили его. Если вам нужно использовать приемник, тогда поместите эту логику в тело конструктора.
Что касается того, почему эта функция законна в Java, вам придется спросить разработчика Java.
Ответ 2
В С# инициализаторы полей являются просто удобной семантикой для разработчика. Компилятор перемещает все инициализаторы полей в тело конструктора ABOVE, где вызов выполняется в базовый конструктор. Итак, поля инициализируются в цепочке предков, и класс инициализируется из базы вниз.
Статические ссылки в порядке, потому что они инициализируются раньше всего.
Ответ 3
Ни в коем случае это не авторитетный ответ, но позвольте мне сделать обоснованное предположение.
Там есть фундаментальное различие, и я думаю, что ответы на другие вопросы связаны с этой разницей.
Он находится в порядке инициализации типа, особенно в контексте наследования.
Итак, как работает инициализация экземпляра?
В С#:
-
все инициализаторы полей экземпляра запускаются сначала, "вверх" цепочка наследования, от большинства полученных до базового класса.
-
то ctors запускают "вниз" цепочку от базы к производной.
Возможность того, что ctors, вызывающие друг друга или (явно) вызывающие ctors базовых классов, не изменяет ситуацию, поэтому я не буду этого делать.
Что в принципе происходит, это выполняется для каждого chass в цепочке, начиная с самого полученного:
Derived.initialize(){
derivedInstance.field1 = field1Initializer();
[...]
Base.Initialize();
Derived.Ctor();
}
Простой пример показывает это:
void Main()
{
new C();
}
class C: B {
public int c = GetInt("C.c");
public C(){
WriteLine("C.ctor");
}
}
class B {
public int b = GetInt("B.b");
public static int GetInt(string _var){
WriteLine(_var);
return 6;
}
public B(){
WriteLine("B.ctor");
}
public static void WriteLine(string s){
Console.WriteLine(s);
}
}
Выход:
C.c
B.b
B.ctor
C.ctor
Это означает, что если доступ к полям в инициализаторе поля был действительным, я мог бы сделать следующее:
class C: B {
int c = b; //b is a field inherited from the base class, and NOT YET INITIALIZED!
[...]
}
В Java:
Длинная интересная статья о инициализации типа здесь. Подводя итог:
Это немного сложнее, потому что помимо понятия инициализаторов полей экземпляра существует понятие инициализатора (необязательного) экземпляра, но здесь его суть:
Все работает вниз цепочка наследования.
Здесь доказательство: (или запустите его самостоятельно в Интернете)
class Main
{
public static void main (String[] args) throws java.lang.Exception
{
new C();
}
}
class C extends B {
{
WriteLine("init C");
}
int c = GetInt("C.c");
public C(){
WriteLine("C.ctor");
}
}
class B {
{
WriteLine("init B");
}
int b = GetInt("B.b");
public static int GetInt(String _var){
WriteLine(_var);
return 6;
}
public B(){
WriteLine("B.ctor");
}
public static void WriteLine(String s){
System.out.println(s);
}
}
Вывод:
init B
B.b
B.ctor
init C
C.c
C.ctor
Это означает, что к моменту запуска инициализатора поля все унаследованные поля уже инициализированы (инициализатором OR ctor в базовом классе), поэтому он достаточно безопасен, чтобы разрешить такое поведение:
class C: B {
int c = b; //b is inherited from the base class, and it already initialized!
[...]
}
В Java, как и на С#, инициализаторы полей выполняются в порядке объявления.
Компилятор Java даже пытается проверить, что инициализаторы полей не вызываются вне порядка *:
class C {
int a = b; //compiler error: illegal forward reference
int b = 5;
}
* В стороне вы можете получить доступ к полям вне порядка, если инициализатор вызывает метод экземпляра для этого:
class C {
public int a = useB(); //after initializer completes, a == 0
int b = 5;
int useB(){
return b; //use b regardless if it was initialized or not.
}
}
Ответ 4
Это потому, что инициализаторы поля перемещаются в конструктор компилятором (если не статичным), и поэтому вам нужно быть явным в вашем конструкторе следующим образом:
class C
{
int i = 5;
byte[] s;
public C()
{
s = new byte[i];
}
}
Ответ 5
Это немного не ответ, но мне нравится думать о чем-либо в теле класса как независимом от последовательности. Он не должен быть последовательным кодом, который нужно оценивать определенным образом - это просто состояние по умолчанию для класса. Если вы используете такой код, вы ожидаете, что я буду оценен до s.
В любом случае, вы можете просто сделать я const (как и должно быть) в любом случае.