Список имеет значение null при наследовании (C #, Unity)

#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 является a MonoBehaviour , то так и есть ClassB , поскольку он наследуется от ClassA … Так что непонятно , что именно вы делаете …

3. У меня есть скрипт с ClassA, который я прикрепляю к GameObject. Этот скрипт содержит некоторую информацию, которую можно изменить в редакторе, и все такое прочее. У меня есть ClassB, у которого есть некоторые методы, например, для сохранения содержимого из ClassA в формат xml. ClassC имеет множество методов OnGUI() для создания простого пользовательского интерфейса, который вызывает методы в ClassB. Причина, по которой я делаю вещи так, как я их делаю, заключается в том, что я подумал, что было бы лучше сделать все соединения основанными на наследовании, вместо того, чтобы присоединять все скрипты к GO и постоянно использовать FindObjectOfType<> .

4. Теперь я знаю, что мог бы сделать это по-другому, но на данный момент мое любопытство взяло верх, и я хотел бы выяснить, как сделать это так, как я пытаюсь это сделать. Я понимаю, что проблема в том, что я использую ключевое слово «new», но какова была бы альтернатива, если не FindObjectOfType<>()?