Cериализация статических объектов в C#

Контекст

С данным вопросом встретился при работе над одним из проектов, в котором очень много настроек. В виду того что добавление новых настроек происходило по ходу разработки то выявилась необходимость сделать класс к которому можно обратится из любого модуля программы. Для этого конечно был использован статический класс, а назвали мы его AppSettings. Конечно можно было бы использовать Properties.Settings… Не буду вдаваться в детали, но этот вариант не подходил.

Суть проблемы

Из за того что класс является статическим обычная сериализация не работает. Давайте проверим. Допустим, у нас есть простой статический класс:
public static class TestStatic
{
	// Fields...
	private static int _Counter;
	public static int Counter
	{
		get { return _Counter; }
		set
		{
		        _Counter = value;
		}
	}       
}


В общем если класс не был бы статическим, можно было бы использовать System.Xml.Serialization.XmlSerializer. Это не наш случай — класс у нас статический и в этом случае, по тому, что XmlSerializer.Serialize метод требует экземпляр класса, а не тип класса, то компилятор выдаст 'Test1.TestStatic' is a 'type' but is used like a 'variable'.

Решение

Погуглив немного, ничего внятного не нашёл. Единственное что я видел это как сеарилизировать класс у которого есть статические поля. В мозгах крутилась только Reflection. Ну чтож, после некоторых экспериментов сделал следующий класс:

public static class SerializeStatic
    {
        public static bool Save(Type static_class, string filename)
        {
            try
            {
                FieldInfo[] fields = static_class.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
                
                object[,] a = new object[fields.Length, 2];
                int i = 0;
                foreach (FieldInfo field in fields)
                {
                    a[i, 0] = field.Name;
                    a[i, 1] = field.GetValue(null);
                    i++;
                };
                Stream f = File.Open(filename, FileMode.Create);
                SoapFormatter formatter = new SoapFormatter();
                formatter.Serialize(f, a);
                f.Close();
                return true;
            }
            catch
            {
                return false;
            }
        }

        public static bool Load(Type static_class, string filename)
        {
            try
            {
                FieldInfo[] fields = static_class.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
                object[,] a;
                Stream f = File.Open(filename, FileMode.Open);
                SoapFormatter formatter = new SoapFormatter();
                a = formatter.Deserialize(f) as object[,];
                f.Close();
                if (a.GetLength(0) != fields.Length) return false;
                int i = 0;
                foreach (FieldInfo field in fields)
                {
                    if (field.Name == (a[i, 0] as string))
                    {
                        if (a[i, 1] != null)
                            field.SetValue(null, a[i, 1]);
                    }
                    i++;
                };
                return true;
            }
            catch
            {
                return false;
            }
        }
    }


Суть проста:
  1. Получаем статические поля:
    FieldInfo[] fields = static_class.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
  2. Создаём матрицу и вбиваем в неё название поля и значение
  3. Ну и потом через SoapFormater записываем в файл.


Загрузка из файла выполняется также. Единственное что по моему мнению стоит заметить: Если в нашем классе, в одном из полей будет другой класс, то в этом случае достаточно будет обозначить второй класс как [Serializable]. То есть если у нас есть такое объявление в TestStatiс:

public static BrowserSettings OperaSettings
{
            get { return _OperaSettings; }
            set
            {
                _OperaSettings = value;
            }
}

Тогда класс BrowserSettings будет выглядеть примерно так:
[Serializable]
public class BrowserSettings
{
     //...
}


Как использовать

Для сохранения используем:
SerializeStatic.Save(typeof(TestStatic), "file.xml");


Для загрузки:
if (!SerializeStatic.Load(typeof(AppSettings), "file.xml")) ;

Комментарии 10

    +9
    Способ интересный, но немного странный.

    Мне кажется, можно было сделать немного проще, применив паттерн «одиночка» (singleton), как советуют тут. И экземпляр отовсюду доступен, и поля сериализуемы.
      0
      Все верно, одиночке тут самое место, к тому же, если вдруг в будущем понадобится несколько экземпляров AppSettings, переделать одиночку будет гораздо проще чем статический класс.
      +1
      Достаточно иметь
      static class ParamsHolder
      {
      public Params Params{get;private set;}
      }

      И проблема сериализации безвелосипедно решена.

      У нас в трехзвенке определен
      public interface IEnvironmentParamsHolder
      {
      EnvironmentParamsInfo Params { get; }
      }

      Интерфейс имеет различные реализации на сервере и на клиенте. Пользователи получают его через IoC
        0
        Извиняюсь за форматирование кода:(
        0
        Тоже так велосипедил, правда делал через protobuf, и по полям и свойствам
        FieldInfo[] fields = type.GetFields(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
        PropertyInfo[] properties = type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
        

        Плюc добавлял проверку на специальный аттрибут, указывающий что данное поле/свойство должно быть сохранено.

        [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
        public class ForSave : Attribute
        {
        }
        .....
        
        if (Attribute.IsDefined(field, typeof(ForSave)))
        


        А по вашему способу вопрос, как он обработает массив [] или Generic?
          +1
          Прошу прощения, а Singleton вы не рассматривали?
            +1
            Не пробовал, я не так давно начал изучать патерны, занимался разработкой драйверов и до патернов не было времени (к сожалению).
              0
              А в драйверах ООП совсем не применяется?
                0
                для того чтобы писать драйвера с использованием DDK используется С, а не С++ и как следствие ни каких ООП там не может быть.
                  0
                  Спасибо, не знал.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое