Declare Function With Property Of Unknown Enumerable Type

I am writing some code at the moment so that the save files for my program can be altered in future versions whilst still being able to load and convert saves from older versions. To do this I have a file that contains a single number to denote the save version and another that contains the actual save. The number will tell it whether the file is the saveVersion1, saveVersion2 or saveVersion3 serializable class.

I then have another function to upgrade it through the various version to the latest. It accepts the current, potentially older, list of saveVersions as a property along with the save version number. There are multiple saves inside a save file, all always of the same save version and in a list of that version.

    public UpgradeExistingSave(System.Object givenCharacters, int givenVersion) {
        if (givenVersion < 2) {
            List<saveVersion2> upgradedCharacters = new List<saveVersion2>();
            foreach (saveVersion1 givenCharacter in givenCharacters) {
                upgradedCharacters.Add(Upgrade1To2(givenCharacter));
            }
            givenCharacters = upgradedCharacters;
        }
        if (givenVersion < 3) {
            List<saveVersion3> upgradedCharacters = new List<saveVersion3>();
            foreach (saveVersion2 givenCharacter in givenCharacters) {
                upgradedCharacters.Add(Upgrade1To2(givenCharacter));
            }
            givenCharacters = upgradedCharacters;
        }
    }

My first thought was to set the property to type of System.Object, as the type of given list of characters (in a save) could be any of the three save types and this seemed like a good ‘Generic’ variable type. However, it returns the following error:

foreach statement cannot operate on variables of type ‘object’ because ‘object’ does not contain a public definition for ‘GetEnumerator’

Is there a better way to do this? What should I declare the variable type as if it is to be recognized as a list as well?

The only thought I have had on this in the last week, and I can’t believe it took me this long to think of it even though it was wrong, was to declare it as follows:

public UpgradeExistingSave(List<System.Object> givenCharacters, int givenVersion) {

But this fails when I pass the function the “givenCharacters” variable and on lines 7 and 14.

//Line 7 & 14 returns "Cannot implicitly convert type 'System.Collections.Generic.List<saveVersion2>' to 'System.Collection.Generic.List<object>'"

This won’t work, as inheritance doesn’t work for generic parameters.

It actually makes sense that it doesn’t work, as otherwise, you could do this:

List<object> myList = new List<string>();
myList.Add(new GameObject());

This also goes for your case - you cannot pass a List as a List, because even if saveVersion2 inherits from Object, a List cannot handle the same things as a List can.

When we do this exact same thing, we’re just using a single class, and have a readonly version-string be set when it’s created. So in your case, it’d be:

class Save {

    private readonly string saveVersion = "3";

    public static Save UpgradeSave(Save save) {
        if(save.saveVersion == "1") {
            save = UpgradeFromV1ToV2(save);
        }
        if(save.saveVersion == "2") {
            save = UpgradeFromV2ToV3(save);
        }
        return save;
    }
}

That requires that you don’t remove fields that you used before. If you want to have multiple versions have different classes, that solution won’t work. What you could do instead would be to have saveVersion1 and saveVersion2 both implement some ISaveVersion interface, and then use a List. You’d have to cast whenever you retrieve an item from that list, but it’d require the least change to your code.

1 Like

I don’t quite understand. Could you explain it a bit more in depth?

I am hoping to be able to add and remove properties for different classes. Such as the character in the save having, say, several status conditions and I remove one because it doesn’t suit the game so much. Or people have initiative in combat and I am planning on replacing initiative with initBase and initRoll, removing the old init stat; that sort of thing.