The simplest method I've had reasonable long-term results with is to enforce (now and forever) your own contract that every class has at least a Version property, which when combined with the type gives you a unique identifier to later tell you how to deserialize to an object. You may also want to create objects from different classes depending on the version # of your stored data, so don't do the versioning within the class, do it with eg a Persister object which works on any object implementing your stateful interface:
First use an application-wide Enum for your application's fundamental object types (not each class):
Public Enum MyObjectTypesEnum
Unspecified = 0
Fish = 1
Cow = 2
End Enum
Then your interface:
Public Interface IStateful
ReadOnly Property Version As Integer
ReadOnly Property ObjectType As MyObjectTypesEnum
Sub SetState(Info As SerializationInfo)
Sub GetState(Info As SerializationInfo)
End Interface
Step 1 in persisting your objects is to store the version and type:
Public Sub SaveTheBasics(Info As SerializationInfo, TheObject as IStateful)
Info.SetInt64(TheObject.Version,"Version")
Info.SetInt64(TheObject.ObjectType,"ObjectType")
This stores all the information you need to cope with different versions in the future:
Dim TheObject as IStateful
Select Case Info.GetInt64("ObjectType")
Case MyObjectTypesEnum.Fish
Select Case Info.GetInt64("Version")
Case Is < 2
TheObject = New OldestFishClass
Case Is < 5
TheObject = New OldFishClass
Case Else
TheObject = New NewFishClass
End Select
Case MyObjectTypesEnum.Cow
...
End Select
TheObject.SetState(Info)
etc etc.
If your application's policy is to insist the data is stored in the latest version, then when loading and upgrading from a previous version's data, you can instantiate the old class, and use that in your newest class's overloaded loading method to upgrade:
Select Case Info.GetInt64("Version")
...
Case Is < 5
Dim TempOldObject As New OldFishClass
TempOldObject.SetState(Info)
TheObject = New NewFishClass
TheObject.SetState(TempOldObject)
Case Else
TheObject = New NewFishClass
TheObject.SetState(Info)
End Select
...
...and make sure your new class knows how to load from its previous version as below.
You can use a base class which implements the Interface for all your stateful objects, and also has any properties all your objects may need:
Public MustInherit Class MyBaseClass
Implements IStateful
Public MustOverride Readonly Property VersionNumber As Integer Implements IStateful.Version
Public MustOverride Readonly Property ObjectType As MyObjectTypesEnum Implements IStateful.ObjectType
Public UniqueID As GUID
...
Public Class NewFishClass
Inherits MyBaseClass
Public Overrides ReadOnly Property VersionNumber As Integer
Get
Return 5
End Get
End Property
Public Overrides ReadOnly Property ObjectType As MyObjectTypesEnum
Get
Return MyObjectTypesEnum.Fish
End Get
End Property
'Overloaded state-setting methods (could be Constructors instead):
Public Sub SetState(Info As SerializationInfo)
Me.ScaleCount = Info.GetInt64("ScaleCount")
End Sub
Public Sub SetState(OldFish As OldFishClass)
'Upgrade from V2:
Me.ScaleCount = OldFish.LeftScales + OldFish.RightScales
End Sub
Public Sub SetState(OldestFish As OldestFishClass)
'Upgrade from V1
Me.ScaleCount = OldFish.Length * OldFish.ScalesPerUnitLength)
End Sub
etc etc.
The overriding principle here is that the contract is contained in Interfaces, and as much as possible the base class does the necessary work of complying with that contract. Then to create your 30 classes you inherit each from the base object. If you've built it right you'll be forced to provide the information necessary for correct versioning and persistance (you're also building in abstraction layers, which of course at some point you'll be grateful for).
In larger projects I extend this sort of thinking so that each object's persistable properties are held in a "State" object which derives from a base "StateBase" class which itself implements an IState interface. Persistance then acts on any StateBase object. I also use a CollectionBase class (which also inherits from my base class) which handles instantiating and filling contained objects. Often your objects need to store references to each other across sessions, so everything needs a unique ID (GUID) to recreate those references - which goes in your base object.