How To Return List Of Unknown Or Generic Type?

I want to essentially do the following:

    public List<?> GetListOfCorrectCustomClass(int givenVersion) {
        if (givenVersion == 0) {
            return new List<saveCharacter0>();
        } else if (givenVersion ==1) {
            return new List<saveCharacter1>();
        } else if (givenVersion ==5) {
            return new List<saveCharacter18>();
        } else if (givenVersion ==6) {
            return new List<saveCharacter4>();
        } else {
            return new List<saveCharacter25>();
        }
    }

Now this clearly doesn’t work. It returns "cannot implicitly convert type List to List. I have also tried:

List<System.Object>
List<System.Type>
List<Object>
List<Monobehaviour>

Is there any way I can do this?

Does your saveCharacter class implement an interface ?

More importantly, how did you plan to use this ? If you want to put its return value using the “var” keyword it wont work since this needs to be known at compile time, it’s just a syntax sugar shortcut to avoid cluttering code with long types.

If you really have to go down that road, instead of returning the value you should call a method:

public void GetListOfCorrectCustomClass(int givenVersion) {
        if (givenVersion == 0) {
            LoadSaveCharacter0();
        } else if (givenVersion == 1) {
           LoadSaveCharacter1();
        } else if (givenVersion == 5) {
           LoadSaveCharacter18();
        } else if (givenVersion == 6) {
           LoadSaveCharacter4();
        } else {
            LoadSaveCharacter025();
        }
    }

If someone has a way to actually solve the return type I’d be really curious to learn about it !

ps: I didn’t mention returning the type “object” then casting it to the proper type since it would be equivalent to the code I suggested, but with an extra return and switch step…

I’m sure there exists a hacky solution to do what you want, but it’s probably a better option to do something like this:

public List<object> GetListOfCorrectCustomClass(int givenVersion)
{
  var list = new List<object>();
  if (givenVersion == 1)
  {
    list.Add(/* whatever objects you need to have in this list */);
  }
  else if
  {
    .
    .
    .
  }
  return list;
}

But even that feels lacking. It’s probably best to create some sort of base class for saveCharacter* classes, and just return that, e.g:

public class SaveCharacterBase
{
  // interface goes here.
}

public class SaveCharacter1 : SaveCharacterBase
{
  // implementation goes here
}

public class abstract SaveCharacter2 : SaveCharacterBase
{
  // implementation goes here
}

public List<SaveCharacterBAse> GetListOfCorrectCustomClass(int givenVersion)
{
  var list = new List<SaveCharacterBase>
  if (givenVersion == 1)
  {
    list.Add(new SaveCharacter1());
  }
  // Rest of condition logic goes here.

  return list;
}

And even that feels icky to me. What are you trying to do exactly?

… why is saveCharacter0 and saveCharacter1 different classes? That’s kinda smelly.

I’m guessing that all of your saveCharacter classes could load from an abstract saveCharacter class, and then the method could have that as it’s return type.

2 Likes

Ok, so a bit more information. In my game there are two save files. One simply contains the version number of the other. So I guess one can be called saveVersionNumber.gd and the other saveGames.gd. I open the first, get the integer contained then use that to open the second. The reason it’s more complex for saveGames.gd is because the contents of the save change ever now and again with a new update. So I am writing a method so the game can determine the correct class the deserialize the file into.

BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/saveversion.gd", FileMode.Open);
versionCurrent = (version)bf.Deserialize(file);
file.Close();


List<?> uncheckedSavedGames = characterVersionScript.GetListOfCorrectCustomClass(versionCurrent.number);
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/saveGames.gd", FileMode.Open);
uncheckedSavedGames = bf.Deserialize(file);
file.Close();

UpgradeSavesToLatestVersion(uncheckedSavedGames, versionCurrent.number)

After deserializing the saveGames.gd file I would pass it into another function that would upgrade it to the current/latest version/custom class so that it was up to date and could take advantage of all the code pertaining to the new save type.

It was my intention to create an empty list and then fill that empty list from the save file.

To do what it this way I need to pass around a list of an unknown type, I need to declare it in the as the return value for GetListOfCorrectCustomClass and I need to declare it as a property of UpgradeSavesToLatestVersion. I also need to be able to access the properties of the class such as uncheckedSavedGames[0].healthMax, uncheckedSavedGames[1].healthCurrent.

All of the fields from the “unchecked save games” that you need to access will be in all of the save game types - otherwise you can’t access them. So you move them up to an abstract superclass, and declare them there. Then you have the list be of that type.

1 Like

I’d actually switch saving systems. Consider something more descriptive, like a JSON format. That way you can add and subtract data at will.

Inheritance
you use an abstract base class for all your SaveCharacter class
mark it serializable so it can be serialized

//create the base class
[System.Serializable]
public abstract class SaveCharacterBase{}

//make your custom class Inherit the base class
[System.Serializable]
public class SaveChara0 : SaveCharacterBase{
        public string PlayerName;
        public int Health,MaxHealth,Gold;
        public int SomeNewThing;
}
[System.Serializable]
public class SaveChara1 : SaveCharacterBase{
        public string PlayerName;
        public int Health,MaxHealth,Gold;
        public int SomeNewThing;
        public string SomeOtherNewStringThing;
}
public class SaveCharacterException: System.Exception{
        public SaveCharacterException():base(){}
        public SaveCharacterException(string message):base(message){}
}
public static class SavedGameUpdater{
    public static List<SaveCharacterBase> GetListOfCorrectCustomClass(int givenVersion) {

        List<SaveCharacterBase> list = new List<SaveCharacterBase>();
        switch (givenVersion) {
        case 0:
            list.Add(new SaveChara0());
            break;
        case 1:
            list.Add(new SaveChara1());
            break;
        default:
            throw new SaveCharacterException("Invalid Version");
            break;
        }
        return list;
    }
public static List<SaveCharacterBase> DeserializeCharacterSave(string name){
        if(!File.Exists(GetFilePath(name))){
            return null;
        }

        BinaryFormatter bf = new BinaryFormatter();
        using(FileStream stream = new FileStream(GetFilePath(name), FileMode.Open)){
            try {
                return bf.Deserialize(stream) as List<SaveCharacterBase>;
            } catch (System.Exception ex) {
                Debug.Log(ex.ToString());
                return null;
            }
        }
    }
}

to get the type

List<SaveCharacterBase>uncheckedSavedGames=SavedGameUpdater.DeserializeCharacterSave("testSave");
foreach(var item in uncheckedSavedGames){
        Debug.Log("Class:"+item)
        var objectType = item.GetType();

        if (objectType == typeof(SaveChara0))
        {
            SaveChara0 save0 = (SaveChara0)saveChar;
            Debug.Log(save0.Health);
        }
        else if (objectType == typeof(SaveChara1))
        {
            SaveChara1 save1 = (SaveChara1)saveChar;
            Debug.Log(save1.Health);
        }

}

and here’s my suggestion:
I’d rather do shaderop’s implementation or BoredMoron’s advice of using JSON.
or use SQLite.
an info about BinaryFormatter
if a field is not serialized and you accessed it, it will return Null or 0,