It looks to me like you could accomplish a lot of this by re-using a common struct between the client instance, the on-the-wire data, and the server instance.
public class Character {
// Data that needs replication over the network.
public struct DTO {
public int level;
public int experience;
public int life;
// ...etc.
}
DTO _replicated;
// Client-only data;
int _localIndex;
// ...etc.
}
When you need to add a new variable to Character, if it's one that needs to be replicated, you add it (once) to the Chatacter.DTO struct, and access it via _replicated.newVariableName.
When you need to send something over the wire, you can grab the whole _replicated struct as a unit, rather than fishing out one variable at a time.
Then on your server, you can do something like this...
public class ServerCharacter {
// Data to replicate.
Character.DTO _clientSnapshot;
Character.DTO _liveData;
// Private server data.
uint _databaseID;
// ...etc.
}
public bool GetDTOShouldSendDTO(ref Character.DTO dataToSend) {
if (_clientSnapshot == _liveData)
return false;
_clientSnapshot = _liveData;
dataToSend = _liveData;
return true;
}
}
As long as your struct is set up correctly, no references or padding, the default comparison operator will do a fast memory compare of the two instances' bits, without you having to check and assign each variable one by one.
You could likely even implement this generically with an unmanaged type constraint to both enforce the no-references rule and save you some repeated code.