#c# #unity3d #inheritance
#c# #unity3d #наследование
Вопрос:
Прошу прощения, если это немного просто, но я не могу понять проблему.
У меня есть три класса:
public class ClassA : MonoBehavior
{
public List<T> list;
void Start()
{
list = new List<T>;
//then fill the list.
}
}
public class ClassB : ClassA
{
void DoesSomething()
{
if(list.Count != 0)
}
}
public interface IClassB { ClassB classB { get; set;} };
public class ClassC: MonoBehaviour, IClassB
{
public ClassB classB { get; set;}
void Start()
{
classB = new ClassB();
}
public void OnButtonClick()
{
classB.DoesSomething();
}
}
Теперь, когда я вызываю функцию DoesSomething() в ClassC, я получаю исключение null в строке, где я проверяю количество списков в ClassB, хотя я инициализировал и заполнил список в начальной функции ClassA. Я отладил его, чтобы проверить, правильно ли он инициализируется, и это происходит.
Так что, возможно, я что-то упускаю в отношении интерфейсов или наследования Unity и C # в целом. Я ценю помощь и заранее благодарю!
Комментарии:
1.
ClassA.Start()
private
поэтому я не вижу, где вы когда-либо в конечном итоге его вызовете.2. @dbc
MonoBehaviour.Start
вызывается как сообщение самой Unity framework. В этом случае, хотя OP создает экземпляр, использованиеnew
которого абсолютно «незаконно» для любого типа, производного отComponent
😉 Смотрите мой ответ ниже
Ответ №1:
Ваш ClassB
a MonoBehaviour
.
Вы никогда не создаете экземпляр a MonoBehaviour
(или вообще что-либо производное от Component
), используя new
ключевое слово! Никогда!
Юмор в сторону, Unity на самом деле должен выдавать предупреждение об этом. Это не имеет смысла. Каждый MonoBehaviour
принадлежит определенному экземпляру GameObject
. И поэтому существует только три допустимых способа создания экземпляра:
- Используйте конструктор
GameObject
для того, чтобы создать новый пустойGameObject
список с присоединенными соответствующими компонентами. - Используйте
AddComponent
существующийGameObject
, чтобы сделать это, добавьте этот компонент вGameObject
. - Используется
Instantiate
либо для клонирования существующегоGameObject
, либо для создания экземпляра сборного экземпляра с соответствующим подключенным компонентом.
Поскольку создание экземпляра через new
является крайне «незаконным», также ни один из Start
методов не будет вызван.
Итак, поскольку кажется, что ваш класс в любом случае не должен быть привязан к определенному GameObject
, но вы собираетесь создать экземпляр, new
удалив MonoBehaviour
наследование, и вместо этого использовать правильный класс.
public class ClassA
{
// And then why not simply initialize the list by default?
// that avoids these kind of initialization problems already right from the beginning
public List<T> list = new List<T>();
// And do the rest of your initialization stuff in a proper constructor
public ClassA()
{
//then fill the list.
}
}
public class ClassB : ClassA
{
public void DoesSomething()
{
if(list.Count != 0) { }
}
}
Затем позже вы можете / должны сразу инициализировать свойство (начиная с c # 6)
public ClassB classB { get; set;} = new ClassB();
Если, однако, ваш ClassA
(и, следовательно ClassB
, фактически должен быть a MonoBehaviour
, то вы не хотите использовать new
, а получаете экземпляр другим способом. Смотрите ранее…
Тем не менее, поскольку Start
вызывается как сообщение, оно вызывается только в последней реализации, поэтому только для ClassB
.
Поэтому для вложенного наследования MonoBehaviour
рекомендуется сделать методы Unityessage, такие как Awake
, Start
etc virtual
, и переопределить их в производных классах, таких как
public class ClassA : MonoBehaviour
{
// And then why not simply initialize the list by default?
// that avoids these kind of initialization problems already right from the beginning
public List<T> list = new List<T>();
protected virtual void Start()
{
//then fill the list.
}
}
public class ClassB : ClassA
{
protected override void Start()
{
base.Start();
// Additional stuff specific for this type
}
public void DoesSomething()
{
if(list.Count != 0) { }
}
}
Комментарии:
1. Спасибо за ответ @derHugo! Я изменил список на инициализацию при объявлении. Это помогло с нулевой ссылкой, но не решило мою проблему полностью. Я хочу, чтобы ClassA был единомышленником, и ClassC тоже. Они оба привязаны к игровому объекту. Класс B не обязательно должен быть привязан к игровому объекту, вот почему я пытаюсь сделать это именно так. Проблема сейчас в том, что у ClassB есть пустой список, хотя я заполнил его при запуске в ClassA. Итак, мой вопрос в некотором роде остается в силе: чего мне не хватает в отношении наследования?
2. @Temeos как уже было сказано: вы не создаете новые экземпляры с помощью
new
. Если вашClassA
является aMonoBehaviour
, то так и естьClassB
, поскольку он наследуется отClassA
… Так что непонятно , что именно вы делаете …3. У меня есть скрипт с ClassA, который я прикрепляю к GameObject. Этот скрипт содержит некоторую информацию, которую можно изменить в редакторе, и все такое прочее. У меня есть ClassB, у которого есть некоторые методы, например, для сохранения содержимого из ClassA в формат xml. ClassC имеет множество методов OnGUI() для создания простого пользовательского интерфейса, который вызывает методы в ClassB. Причина, по которой я делаю вещи так, как я их делаю, заключается в том, что я подумал, что было бы лучше сделать все соединения основанными на наследовании, вместо того, чтобы присоединять все скрипты к GO и постоянно использовать FindObjectOfType<> .
4. Теперь я знаю, что мог бы сделать это по-другому, но на данный момент мое любопытство взяло верх, и я хотел бы выяснить, как сделать это так, как я пытаюсь это сделать. Я понимаю, что проблема в том, что я использую ключевое слово «new», но какова была бы альтернатива, если не FindObjectOfType<>()?