Пример сохранения и чтения файла. Есть ещё много других способов. Для поддержки версий файла нужно внутри репозитория создать Dictionary{TKey, TValue}, где TKey - это номер версии, а TValue - сериализатор, десериализатор файла (каждый пишет и читает по своему). Далее, когда читаешь файл, то сначала читаешь его версию, а потом уже вызываешь нужную версию сериализатора и десериализатора.
Program.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
namespace GameSaveExample
{
class Program
{
static async Task Main(string[] args)
{
try
{
var rootDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"AppName");
using var repository = new GameSaveRepository(rootDirectory, "SaveFile.bin");
Console.WriteLine("Первоначальная загрузка данных и вывод на экран:");
SaveData data = await repository.Load();
Display(data);
Console.WriteLine("Сохраняем изменённые данные и выводим на экран:");
data.Id += 5;
await repository.Save(data);
Display(data);
Console.WriteLine("-------------------------");
Console.WriteLine("Загружаем данные из файла и выводим на экран:");
data = await repository.Load();
Display(data);
Process.Start("explorer.exe", rootDirectory);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static void Display(SaveData data)
{
Console.WriteLine($"Version: {data.Version}, ID: {data.Id}");
}
}
}
GameSaveRepository.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace GameSaveExample
{
/// <summary>
/// Класс, который умеет сохранять данные и загружать.
/// </summary>
public class GameSaveRepository : IDisposable
{
private readonly string _rootDirectory;
private readonly string _fileName;
private readonly string _pathToFile;
private readonly SemaphoreSlim _semaphore;
public GameSaveRepository(string rootDirectory, string fileName)
{
_rootDirectory = rootDirectory;
_fileName = fileName;
_pathToFile = Path.Combine(_rootDirectory, fileName);
_semaphore = new SemaphoreSlim(1);
}
public async Task<SaveData> Load()
{
await _semaphore.WaitAsync();
var data = new SaveData();
try
{
await Task.Run(() =>
{
if (File.Exists(_pathToFile))
{
using var stream = File.OpenRead(_pathToFile);
using var reader = new BinaryReader(stream);
data.Version = reader.ReadInt32();
data.Id = reader.ReadInt32();
}
});
return data;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
throw;
}
finally
{
_semaphore.Release();
}
}
public async Task Save(SaveData data)
{
await _semaphore.WaitAsync();
try
{
await Task.Run(() =>
{
if (!Directory.Exists(_rootDirectory))
{
Directory.CreateDirectory(_rootDirectory);
}
using var stream = File.OpenWrite(_pathToFile);
using var writer = new BinaryWriter(stream);
writer.Write(data.Version);
writer.Write(data.Id);
});
}
catch (Exception ex)
{
Debug.WriteLine(ex);
throw;
}
finally
{
_semaphore.Release();
}
}
public void Dispose()
{
_semaphore?.Dispose();
}
}
}
SaveData.cs
namespace GameSaveExample
{
public class SaveData
{
/// <summary>
/// Версия формата файла. Добавил, удалил или изменил свойство, тогда поднимаем версию.
/// На каждую версию свой класс сериализатор-десериализатор.
/// </summary>
public int Version { get; set; }
/// <summary>
/// Какие-то данные.
/// </summary>
public int Id { get; set; }
}
}