Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > .NET Frameworks (точка нет фреймворки) > Общие вопросы .NET
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 28.10.2009, 11:04   #1
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию Сериализуемый Singletone

Я делал механизм сохранения настроек программы (C#). Как только я познакомился с механизмом Settings.settings, я сразу от него отказался (см. другой топик). :)

Первый очевидный вариант - сделать статический класс с переменными настроек. Но я собирался использовать сериализацию в XML файл, а статические объекты не сериализуются. Поэтому мне пришлось изучить шаблоны проектирования (паттерны), в частности, синглтон (одиночка). Подробнее про шаблоны в другом топике.

Как оказалось, то ли у сериализатора проблемы с наследованием (см здесь), то ли проблема в самом синглтоне. В общем, мне пришлось спроектировать свой класс - сериализуемый аналог синглтона. Вот что получилось.
Код:
using System;
using System.Text;
using System.IO;
using System.Xml.Serialization;

namespace Singleton
{
	public sealed class Options
	{
		static public Options Instance = new Options();	// единственный экземпляр класса настроек

		public int param1;	// тут идут сами переменные настроек

		private Options()	// закрытый конструктор заперщает создавать экземпляры класса
		{
			param1 = 17;	// значения по умолчанию
		}

		static public void Save (string FileName)	// сохранение в файл
		{
			StreamWriter Writer = new StreamWriter (FileName);
			XmlSerializer Serializer = new XmlSerializer (typeof(Options));
			Options TmpOptions = Instance;				// делаем временную нестатическую копию...
			Serializer.Serialize (Writer, TmpOptions);	// ...и сериализуем её
			Writer.Close();
		}
	}


	class Program
	{
		static void Main()
		{
			try
			{
				Options.Instance.param1 = 12;
				Options.Save ("options.xml");
			}
			catch (Exception exc)
			{
				Console.WriteLine (exc.Message);
				Console.ReadKey();
			}
		}
	}
}
Может, кому-нибудь пригодится. Может, у кого замечания будут - я ещё не очень разбираюсь во всех sealed, readonly, и прочих модификаторах.

Последний раз редактировалось ds.Dante; 28.10.2009 в 11:17.
ds.Dante вне форума Ответить с цитированием
Старый 28.10.2009, 11:19   #2
Hollander
Участник клуба
 
Аватар для Hollander
 
Регистрация: 03.05.2007
Сообщений: 1,189
По умолчанию

Ну вообще-то не очень. Загрузки нету. Я бы сделал так(точнее делаю):

Код:
namespace Options
{
	public class Options
	{
		private int param;
		public int Param
		{
			get { return param; }
			set { param = value; }
		}

		[NonSerialized]
		private static object instanceSync = new object();

		[NonSerialized]
		private static Options instance;

		[XmlIgnore]
		public static Options Instance
		{
			get
			{
				if (instance == null)
				{
					lock (instanceSync)
					{
						if (instance == null)
							instance = Load(FilePath);
					}
				}

				return instance;
			}
		}

		public static string FilePath
		{
			get
			{
				return Path.Combine(
					имя_папки "options.xml");
			}
		}
	

		private static Options Load(string filePath)
		{
			if (!File.Exists(filePath))
				return new Options();

			try
			{
				XmlSerializer serializer = new XmlSerializer(typeof(Options));
				using (FileStream stream = new FileStream(filePath, FileMode.Open))
				{
					Options result = (MasterOptions)serializer.Deserialize(stream);
					return result;
				}
			}
			catch (Exception)
			{
				return new Options();
			}
		}

		public void Save()
		{
			Save(FilePath);
		}
		

		private void Save(string filePath)
		{
			XmlSerializer serializer = new XmlSerializer(typeof(Options));
			using (FileStream stream = new FileStream(filePath, FileMode.Create))
				serializer.Serialize(stream, this);
		}


		
}
}
Hollander вне форума Ответить с цитированием
Старый 28.10.2009, 12:22   #3
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию

Несколько вопросов.

1)
Код:
private int param;
public int Param
{
	get { return param; }
	set { param = value; }
}
Почему бы не сделать просто public int Param?

2) Чем отличается [NonSerialized] и [XmlIgnore]? Где можно посмотреть все эти атрибуты? Необходимы ли они здесь для статических членов, или это для наглядности?

3) Почему именно здесь нужен lock(instanceSync)? Почему не нужен в других местах?

4) Как я понял, в программе нужно создавать глобальный экземпляр класса Options?
ds.Dante вне форума Ответить с цитированием
Старый 28.10.2009, 13:34   #4
Hollander
Участник клуба
 
Аватар для Hollander
 
Регистрация: 03.05.2007
Сообщений: 1,189
По умолчанию

1. Просто стиль
2. Разница между ними заключается в используемых сериализаторах/форматерах. Это кусок рабочей программы, немного переделанный под твой случай и где в моем коде используются форматеры. Есть SoapFormatter и BinaryFormatter, для них нудо использовать [NonSerialized]. Для XmlSerializer - [XmlIgnore]. Дело в то, что XmlSerializer сам игнорирует не public поля. А SoapFormatter и BinaryFormatter не делает этого. Почитать можно тут http://kaushalp.blogspot.com/2005/06...rformance.html.
3. Ты работаешь со статических объектом, поэтому нужна синхронизация. Т.е. может возникнуть ситуация, когда ты изменяешь данный объект одновременно из двух мест.
4. Нет. В программе ты пользуешься точно также как в твоем примере. Единственное, что при первом обращении произойдет загрузка(если есть файл) настроек.
Hollander вне форума Ответить с цитированием
Старый 28.10.2009, 16:11   #5
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию

Спасибо за ответ. Пункт 2 буду переваривать несколько дней. :)
ds.Dante вне форума Ответить с цитированием
Старый 11.01.2010, 11:32   #6
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию

Сегодня вернулся с "отпуска". :)

В выходные решил поднять старую тему, и с новыми знаниями (я на шарпе всего несколько месяцев программирую) ещё раз попробовать сделать класс настроек.

В общем, статический класс ничем не хуже синглтона, скорее даже это встроенный в язык синглтон.

Непосредственно значения настроек проще всего хранить в отдельной структуре (OptionsValues), во-первых, чтобы не использовать аттрибуты NonSerialized, а во-вторых, из-за невозможности сериализовать статические типы. К тому же в этой структуре можно хранить значения настроек по умолчанию (DefaultOptions) и максимальные допустимые значения (MaximumOptions).

Также в проекте есть форма окна с настройками OptionsForm, экземпляр которой содержится в классе. Я так сделал, потому что я планирую использовать этот класс в плагинах, и набор параметров заранее будет неизвестен.

В конце прикреплён весь проект с формой OptionsForm и с демонстрацией работы класса.

Код:
using System;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;

namespace Options
{
	public struct OptionsValues							// pure values
	{
		public int SettingA;
		public float SettingB;
		public bool[] SettingArray;
	}


	public static class Options
	{
		static OptionsValues CurrentOptions;
		static readonly OptionsValues DefaultOptions;
		static readonly OptionsValues MaximumOptions;

		static OptionsForm OptionsWindow = null;

		static Options ()								// startup initialization
		{
			const string DefaultFilename = "options.xml";

			DefaultOptions = new OptionsValues ();		// setting default options
			DefaultOptions.SettingA = 10;
			DefaultOptions.SettingB = 17.42F;
			DefaultOptions.SettingArray = new bool[5]
			{
				true,
				false,
				false,
				true,
				false
			};

			MaximumOptions = new OptionsValues ();		// setting maximum options
			MaximumOptions.SettingA = 65535;
			MaximumOptions.SettingB = 1000000F;

			try
			{
				Load (DefaultFilename);
			}
			catch (Exception)
			{
				Reset ();
			}

			OptionsWindow = new OptionsForm ();
			OptionsWindow.FormClosing += new FormClosingEventHandler (OptionsWindowClosing);	// save options on window close
		}

		public static OptionsValues Current				// get current values
		{
			get
			{
				return CurrentOptions;
			}
		}

		public static Form Window						// get options window
		{
			get
			{
				OptionsWindow.CtrlSettingA.Value = CurrentOptions.SettingA;				// set window controls
				OptionsWindow.CtrlSettingB.Text = CurrentOptions.SettingB.ToString ();
				OptionsWindow.CtrlSettingArray.Items.Clear ();
				for (int i=0; i<CurrentOptions.SettingArray.Length; i++)
					OptionsWindow.CtrlSettingArray.Items.Add ("Setting" + (i+1).ToString (), CurrentOptions.SettingArray[i]);

				return OptionsWindow;
			}
		}

		static void OptionsWindowClosing (object sender, FormClosingEventArgs e)	// save on close
		{
			CurrentOptions.SettingA = (int)OptionsWindow.CtrlSettingA.Value;		// get options from controls

			try
			{
				CurrentOptions.SettingB = Convert.ToSingle (OptionsWindow.CtrlSettingB.Text);
			}
			catch (Exception) { }

			for (int i=0; i<CurrentOptions.SettingArray.Length; i++)
				CurrentOptions.SettingArray[i] = OptionsWindow.CtrlSettingArray.GetItemChecked (i);
		}

		public static void Reset ()						// set defaults
		{
			CurrentOptions = DefaultOptions;
		}

		public static void Save (string Filename)		// write XML
		{
			XmlWriter Writer = XmlWriter.Create (Filename);
			XmlSerializer Serializer = new XmlSerializer (typeof (OptionsValues));
			Serializer.Serialize (Writer, CurrentOptions);
			Writer.Close ();
		}

		public static void Load (string Filename)		// read XML
		{
			XmlReader Reader = XmlReader.Create (Filename);
			XmlSerializer Deserializer = new XmlSerializer (typeof (OptionsValues));
			CurrentOptions = (OptionsValues)Deserializer.Deserialize (Reader);
			Reader.Close ();
		}
	}
}
Вложения
Тип файла: rar Serializator.rar (14.5 Кб, 7 просмотров)

Последний раз редактировалось ds.Dante; 12.01.2010 в 10:51.
ds.Dante вне форума Ответить с цитированием
Старый 11.01.2010, 15:08   #7
Hollander
Участник клуба
 
Аватар для Hollander
 
Регистрация: 03.05.2007
Сообщений: 1,189
По умолчанию

Выскажу свое мнение.

1. У тебя класс, который загружает данные при каждом обращении. У тебя пример простой, ты считываешь мало данных, а в реальной жизни это может быть и 20 мб и представь ситуацию:
Код:
Options.Options.Window.ShowDialog ();
Options.Options.Save ("options.xml");
Options.Options.Load ("options.xml");
Options.Options.ЕщеКакойНибудьМетод();
Options.Options.И_их_сколь_угодно_много();
// думаю суть понятна, тем более что обычно такой механизм используется для чтения настроек
И ты на каждой строчке читаешь с диска по 20 мб - это очень плохо.

2. Поскольку ты объявил все переменные как static то нету смысла вообще создавать этот класс. Т.к. получается, что у тебя обычные статические переменные, которым присвоили значения. Они просто собраны в одном классе.

3. Работа с формой из static класса - совсем плохо. Вообще советуют не использовать статические переменные. Исключение составляет синглтон с настройками. И когда подписываешься на событие (у тебя Form_Closing) не забывай отписаться.
Hollander вне форума Ответить с цитированием
Старый 11.01.2010, 15:40   #8
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию

Цитата:
Сообщение от Hollander Посмотреть сообщение
И ты на каждой строчке читаешь с диска по 20 мб - это очень плохо.
Не понял. Настройки читаются только в Read(), а обращение к ним через Options.Current.


Цитата:
Сообщение от Hollander Посмотреть сообщение
2. Поскольку ты объявил все переменные как static то нету смысла вообще создавать этот класс.
Это тот же самый синглтон. И это не только переменные, собранные в одном классе, но и функции для работы с ними (чтение из файла, и т. д.)


Цитата:
Сообщение от Hollander Посмотреть сообщение
3. Работа с формой из static класса - совсем плохо. Вообще советуют не использовать статические переменные.
Прошу поподробнее. Лучше ссылками.


Цитата:
Сообщение от Hollander Посмотреть сообщение
И когда подписываешься на событие (у тебя Form_Closing) не забывай отписаться.
А зачем отписываться?
ds.Dante вне форума Ответить с цитированием
Старый 11.01.2010, 17:30   #9
Hollander
Участник клуба
 
Аватар для Hollander
 
Регистрация: 03.05.2007
Сообщений: 1,189
По умолчанию

1. Сорри, не заметил, просто тебе нужно дополнительно вызывать команду Load(чего не надо в сингтоне).
2. Это не совсем синглтон. Пример синглтона есть выше.
3. Тут описаны советы, в них подводные камни http://msdn.microsoft.com/ru-ru/library/dd335949.aspx
4. Отписываться от подписанных событий необходимо всегда. Если не отписываться, то у твоего объекта, который подписался будет висеть ссылка на событие, что не позволит GC(сборщику мусора) очистить память.
Hollander вне форума Ответить с цитированием
Старый 12.01.2010, 10:50   #10
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию

1) Немного изменил конструктор (отредактировал пост):

Код:
const string DefaultFilename = "options.xml";

...

try
{
	Load (DefaultFilename);
}
catch (Exception)
{
	Reset ();
}

2) Тем не менее, существование статического класса кажется мне вполне обоснованным.


3) Не нашёл в статье чего-то применительного к моим формам (хотя с многопоточностью у меня, конечно, проблемы). Ты имел в виду, что в статическом классе я не смогу отписаться от события?

4) Кстати, о стиле.
Цитата:
Сообщение от ds.Dante Посмотреть сообщение
Код:
private int param;
public int Param
{
	get { return param; }
	set { param = value; }
}
Почему бы не сделать просто public int Param?
Цитата:
Сообщение от Hollander Посмотреть сообщение
Просто стиль
Такой стиль оправдан каким-нибудь удобством или гибкостью?

Последний раз редактировалось ds.Dante; 12.01.2010 в 10:55.
ds.Dante вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц