Почему класс не может расширить свой собственный вложенный класс в C #?

StackOverflow https://stackoverflow.com/questions/265585

  •  06-07-2019
  •  | 
  •  

Вопрос

Например:

public class A : A.B
{
    public class B { }
}

Который генерирует эту ошибку от компилятора:

Циклическая зависимость базового класса с участием 'A' и 'A.B'

Я всегда полагал, что вложенный класс ведет себя точно так же, как обычный класс, за исключением специальных правил, касающихся доступа к закрытым членам внешнего класса, но я предполагаю, что между двумя классами происходит какое-то неявное наследование?

Это было полезно?

Решение

Насколько я могу судить, здесь нет никакого неявного наследования.Я бы ожидал, что все будет в порядке - хотя я могу представить странность, если бы A и B были общими.

Это указано в разделе 10.1.4 спецификации:

Когда класс B является производным от класса A, это ошибка времени компиляции для A, чтобы зависеть от B.Класс напрямую зависит от своего прямого базового класса (если таковой имеется) и напрямую зависит от класса в который сразу вложенных (если есть).Учитывая это определение, полный набор классов, от которых зависит класс, является транзитивным замыкание отношения "напрямую зависит от ".

Я выделил соответствующий раздел.

Это объясняет, почему компилятор отклоняет его, но не почему язык запрещает это.Интересно, есть ли ограничение CLI...

Редактировать:Итак, я получил ответ от Эрика Липперта.В принципе, это было бы технически возможно (в CLI нет ничего, что запрещало бы это), но:

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

В ветке электронного письма также было отмечено, что это сделало бы подобные вещи действительными:

A.B x = new A.B.B.B.B.B.B.B.B.B.B.B.B();

...но это уже было бы (как отметил Тинистер) справедливо, если бы B было производным от A.

Вложенность + наследование = странность...

Другие советы

Это не C #, а компилятор. Одной из задач компилятора является размещение класса в памяти, который представляет собой набор базовых типов данных, указателей, указателей функций и других классов.

Он не может создать макет для класса A, пока не узнает, что такое макет класса B. Он не может знать, что такое макет класса B, пока не закончит макет класса A. Круговая зависимость.

Я думаю, что вложенность означает, что вложенный тип является частью определения типа вложенности. С такой интерпретацией ограничение имеет смысл, потому что в то время, когда компилятор достигает определения A, A.B еще не определено, и даже в конце A оно уже определено в терминах A.B.

Относительно вопросов о том, что я пытался сделать:

По сути, я хотел создать класс, который имел композиционные отношения с самим собой, но я не хотел, чтобы содержащийся объект содержал другие объекты и, следовательно, создавал цепочку со многими "A has-a A has- А имеет-А имеет ... " отношения. Так что в то время я думал сделать что-то вроде этого:

public class A : A.AA
{
    public class AA
    {
        // All of the class's logic
    }

    private AA _containedObject;
}

Который в то время казался довольно гладким, но, оглядываясь назад, я не уверен ...

Я порылся в Google и не нашел хорошего обсуждения, поэтому решил опубликовать его здесь.

Однако в комментариях в блоге Эрика Липперта он приводит примеры класса, реализующего вложенный интерфейс, а также класс, реализующий универсальный интерфейс с вложенным классом в качестве аргумента типа (который не компилируется, и он вызывает "ошибку" в текущем компиляторе). Оба этих примера касаются интерфейсов, поэтому мне было интересно, есть ли какие-то особые правила для вложенных классов. И, кажется, есть.

Мне удалось избежать этого (по крайней мере, с помощью интерфейсов), унаследовав от отдельного класса, содержащего вложенные интерфейсы. (В моем сценарии я также возвращаю ссылки на эти интерфейсы.)

Вместо:

public class MyClass<T1, T2, T3> :
   MyClass<T1, T2, T3>.Interface
where T1 : ...
where T2 : ... 
where T3 : ... {
   public interface Interface { Interface SomeMethod(); }

   Interface Interface.SomeMethod() {
      ...
   }
}

// compile error: Circular base class dependency

Сделайте что-то вроде этого:

public sealed class MyClassInterfaces<T1, T2, T3>
where T1 : ...
where T2 : ... 
where T3 : ... {
   public interface Interface { Interface SomeMethod(); }
}

sealed class MyClass<T1, T2, T3> :
   MyClassInterfaces<T1, T2, T3>.Interface
where T1 : ...
where T2 : ... 
where T3 : ... {
   MyClassInterfaces<T1, T2, T3>.Interface
   MyClassInterfaces<T1, T2, T3>.Interface.SomeMethod() {
      ...
   }
}

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

public abstract class MyClassInterfaces<T1, T2, T3>
where T1 : ...
where T2 : ... 
where T3 : ... {
   public interface Interface { Interface SomeMethod(); }
}

sealed class MyClass<T1, T2, T3> :
   MyClassInterfaces<T1, T2, T3>,
   MyClassInterfaces<T1, T2, T3>.Interface
where T1 : ...
where T2 : ... 
where T3 : ... {
   Interface Interface.SomeMethod() {
      ...
   }
}

Это не имеет смысла для меня ... Вы пытаетесь расширить то, что не существует !!! Класс B существует только в рамках класса A, и поэтому я думаю, что есть какое-то наследство.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top