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.