Слово "
Введение в язык частных случаев усложняет его и свидетельствует о некоторых изъянах, для преодоления которых и вводятся частные случаи. Например, введение структур в язык C# позволило определять классы как Подробнее о развернутых и ссылочных типах см. лекцию 17. |
Отметим одно важное IComparable, реализация которого позволяет сравнивать объекты не только на равенство, но и на "больше", "меньше".
Давайте опишем некоторый
public interface IProps
{
void Prop1(string s);
void Prop2 (string name, int val);
}
У этого
Класс, наследующий
public class Clain:IProps
{
public Clain() {}
public void Prop1(string s)
{
Console.WriteLine(s);
}
public void Prop2(string name, int val)
{
Console.WriteLine("name = {0}, val ={1}", name, val);
}
}//Clain
Класс реализует методы private, и уточняет имя метода именем
public class ClainP:IProps
{
public ClainP(){ }
void IProps.Prop1(string s)
{
Console.WriteLine(s);
}
void IProps.Prop2(string name, int val)
{
Console.WriteLine("name = {0}, val ={1}", name, val);
}
}//class ClainP
Класс ClainP реализовал методы IProps, но сделал их закрытыми и недоступными для вызова клиентами и наследниками класса. Как же получить доступ к закрытым методам? Есть два способа решения этой проблемы:
IProps, полученный преобразованием ( ClainP. Этому объекту доступны закрытые методы В чем главное достоинство ClainP:
public void MyProp1(string s)
{
((IProps)this).Prop1(s);
}
public void MyProp2(string s, int x)
{
((IProps)this).Prop2(s, x);
}
Как видите, методы переименованы и получили другие имена, под которыми они и будут известны клиентам класса. В обертке для вызова закрытого метода пришлось использовать this к интерфейсному классу IProps.
Создать объект класса new нельзя. Тем не менее, можно объявить объект Clain, ClainP, так и с объектами интерфейсного класса IProps:
public void TestClainIProps()
{
Console.WriteLine("Объект класса Clain вызывает
открытые методы!");
Clain clain = new Clain();
clain.Prop1(" свойство 1 объекта");
clain.Prop2("Владимир", 44);
Console.WriteLine("Объект класса IProps вызывает
открытые методы!");
IProps ip = (IProps)clain;
ip.Prop1("интерфейс: свойство");
ip.Prop2 ("интерфейс: свойство",77);
Console.WriteLine("Объект класса ClainP вызывает
открытые методы!");
ClainP clainp = new ClainP();
clainp.MyProp1(" свойство 1 объекта");
clainp.MyProp2("Владимир", 44);
Console.WriteLine("Объект класса IProps вызывает
закрытые методы!");
IProps ipp = (IProps)clainp;
ipp.Prop1("интерфейс: свойство");
ipp.Prop2 ("интерфейс: свойство",77);
}
Этот пример демонстрирует работу с классом, где все наследуемые методы
(рис 19.1) Наследование интерфейса. Две стратегии
При
Проблема
Стратегия
Другая стратегия исходит из того, что, несмотря на единую сигнатуру, методы разных
Итак,
public interface IProps
{
void Prop1(string s);
void Prop2 (string name, int val);
void Prop3();
}
public interface IPropsOne
{
void Prop1(string s);
void Prop2 (int val);
void Prop3();
}
У двух
public class ClainTwo:IProps,IPropsOne
{
/// <summary>
/// склеивание методов двух интерфейсов
/// </summary>
/// <param name="s"></param>
public void Prop1 (string s)
{
Console.WriteLine(s);
}
/// <summary>
/// перегрузка методов двух интерфейсов
/// </summary>
/// <param name="s"></param>
/// <param name="x"></param>
public void Prop2(string s, int x)
{
Console.WriteLine(s + "; " + x);
}
public void Prop2 (int x)
{
Console.WriteLine(x);
}
/// <summary>
/// переименование методов двух интерфейсов
/// </summary>
void IProps.Prop3()
{
Console.WriteLine("Свойство 3 интерфейса 1");
}
void IPropsOne.Prop3()
{
Console.WriteLine("Свойство 3 интерфейса 2");
}
public void Prop3FromInterface1()
{
((IProps)this).Prop3();
}
public void Prop3FromInterface2()
{
((IPropsOne)this).Prop3();
}
}
Для первого из методов с совпадающей сигнатурой выбрана стратегия
Приведу пример работы с объектами класса и интерфейсными объектами:
public void TestCliTwoInterfaces()
{
Console.WriteLine("Объект ClainTwo вызывает методы двух интерфейсов!");
Cli.ClainTwo claintwo = new Cli.ClainTwo();
claintwo.Prop1("Склейка свойства двух интерфейсов");
claintwo.Prop2("перегрузка ::: ",99);
claintwo.Prop2(9999);
claintwo.Prop3FromInterface1();
claintwo.Prop3FromInterface2();
Console.WriteLine("Интерфейсный объект вызывает методы 1-го
интерфейса!");
Cli.IProps ip1 = (Cli.IProps)claintwo;
ip1.Prop1("интерфейс IProps: свойство 1");
ip1.Prop2("интерфейс 1 ", 88);
ip1.Prop3();
Console.WriteLine("Интерфейсный объект вызывает методы 2-го
интерфейса!");
Cli.IPropsOne ip2 = (Cli.IPropsOne)claintwo;
ip2.Prop1("интерфейс IPropsOne: свойство1");
ip2.Prop2(7777);
ip2.Prop3();
}
Результаты работы тестирующей процедуры показаны на рис. 19.2.
(рис 19.2) Решение проблемы коллизии имен
Проблема C является наследником классов A и B, а те, в свой черед, являются наследниками класса Parent, то класс наследует свойства и методы своего предка Parent дважды, один раз получая их от класса A, другой от - B. Это явление называется еще дублирующим наследованием. Для классов ситуация осложняется тем, что классы A и B могли по-разному переопределить методы родителя и для потомков предстоит сложный выбор реализации.
Для
Начнем наш пример с наследования
public interface IParent
{
void ParentMethod();
}
public interface ISon1:IParent
{
void Son1Method();
}
public interface ISon2:IParent
{
void Son2Method();
}
Два сыновних
public class Pars:ISon1, ISon2
{
public void ParentMethod()
{
Console.WriteLine("Это метод родителя!");
}
public void Son1Method()
{
Console.WriteLine("Это метод старшего сына!");
}
public void Son2Method()
{
Console.WriteLine("Это метод младшего сына!");
}
}//class Pars
Класс обязан реализовать метод ParentMethod, приходящий от обоих IParent, лучшей стратегией реализации является
public void TestIParsons()
{
Console.WriteLine("Объект класса вызывает методы трех
интерфейсов!");
Cli.Pars ct = new Cli.Pars();
ct.ParentMethod();
ct.Son1Method();
ct.Son2Method();
Console.WriteLine("Интерфейсный объект 1 вызывает свои
методы!");
Cli.IParent ip = (IParent)ct;
ip.ParentMethod();
Console.WriteLine("Интерфейсный объект 2 вызывает свои
методы!");
Cli.ISon1 ip1 = (ISon1)ct;
ip1.ParentMethod();
ip1.Son1Method();
Console.WriteLine("Интерфейсный объект 3 вызывает свои
методы!");
Cli.ISon2 ip2 = (ISon2)ct;
ip2.ParentMethod();
ip2.Son2Method();
}
Результаты работы тестирующей процедуры показаны на рис. 19.3.
(рис 19.3) Дублирующее наследование интерфейсов
Рассмотрим несколько
Часто, когда создается класс, желательно задать IComparable. Этот CompareTo (object obj), возвращающий целочисленное значение, положительное, отрицательное или равное нулю, в зависимости от выполнения отношения "больше", "меньше" или "равно".
Как правило, в классе вначале определяют метод CompareTo, а после этого вводят перегруженные операции, чтобы выполнять сравнение объектов привычным образом с использованием знаков операций отношения.
Давайте введем Person, рассмотренном в лекции 16, сделав этот класс наследником IComparable. Реализуем в этом классе метод CompareTo:
public class Person:IComparable
{
public int CompareTo( object pers)
{
const string s = "Сравниваемый объект не принадлежит классу Person";
Person p = pers as Person;
if (!p.Equals(null))
return (fam.CompareTo(p.fam));
throw new ArgumentException (s);
}
// другие компоненты класса
}
Поскольку аргумент метода должен иметь универсальный тип object, то перед выполнением сравнения его нужно привести к типу Person. Это as, позволяющую проверить корректность выполнения
При is и as. Логическое выражение (obj is T) истинно, если объект obj имеет тип T. Оператор присваивания ( obj = P as T; ) присваивает объекту obj объект P, приведенный к типу T, если такое null. Семантику as можно выразить следующим условным выражением: (P is T) ? (T)P : (T)null.
Заметьте также, что при проверке на значение null используется отношение Equals, а не обычное равенство, которое будет переопределено.
Person задается как отношение порядка на фамилиях персон. Так как строки наследуют IComparable, то для фамилий персон вызывается метод CompareTo, его результат и возвращается в качестве результата метода CompareTo для персон. Если аргумент метода не будет соответствовать нужному типу, то выбрасывается исключение со специальным уведомлением.
Конечно, сравнение персон может выполняться по разным критериям: возрасту, росту, зарплате. Общий подход к сравнению персон будет рассмотрен в следующей лекции 20.
Введем теперь в нашем классе Person
public static bool operator <(Person p1, Person p2)
{
return (p1.CompareTo(p2) < 0);
}
public static bool operator >(Person p1, Person p2)
{
return (p1.CompareTo(p2) > 0);
}
public static bool operator <=(Person p1, Person p2)
{
return (p1.CompareTo(p2) <= 0);
}
public static bool operator >=(Person p1, Person p2)
{
return (p1.CompareTo(p2) >=0);
}
public static bool operator ==(Person p1, Person p2)
{
return (p1.CompareTo(p2) == 0);
}
public static bool operator !=(Person p1, Person p2)
{
return (p1.CompareTo(p2) != 0);
}
Как обычно, приведу тестовый пример, проверяющий работу с введенными методами:
public void TestCompare()
{
Person poet1 = new Person("Пушкин");
Person poet2 = new Person("Лермонтов");
Person poet3 = new Person("Пастернак");
Person poet4 = new Person("Мандельштам");
Person poet5 = new Person("Ахматова");
Person poet6 = new Person("Цветаева");
Console.WriteLine("{0} > {1} = {2}", poet1.Fam,
poet2.Fam, (poet1 > poet2));
Console.WriteLine("{0} >= {1} = {2}", poet3.Fam,
poet4.Fam, (poet3 >= poet4));
Console.WriteLine("{0} != {1} = {2}", poet5.Fam,
poet6.Fam, (poet5 != poet6));
}
Вот результаты работы этого теста.
(рис 19.4) Сравнение персонКонечно, заданный нами порядок не имеет никакого отношения к поэтическому дару, а лишь говорит об относительном расположении фамилий поэтов в словарях.
MemberwiseClone, наследуемого от прародителя object. Единственное, что нужно помнить: этот метод защищен, он не может быть вызван у клиента класса. Поэтому
Давайте обеспечим эту возможность для класса Person, создав в нем соответствующий метод:
public Person StandartClone()
{
Person p = (Person)this.MemberwiseClone();
return(p);
}
Теперь клиенты класса могут легко создавать поверхностные
public void TestStandartClone()
{
Person mother = new Person("Петрова Анна");
Person daughter = new Person("Петрова Ольга");
Person son = new Person("Петров Игорь");
mother[0] = daughter;
mother[1] = son;
Person mother_clone = mother.StandartClone();
Console.WriteLine("Дети матери: {0}",mother.Fam);
Console.WriteLine (mother[0].Fam);
Console.WriteLine (mother[1].Fam);
Console.WriteLine("Дети клона: {0}",mother_clone.Fam);
Console.WriteLine (mother_clone[0].Fam);
Console.WriteLine (mother_clone[1].Fam);
}
При создании mother. Обратите внимание: при работе с полем children, задающим детей, используется индексатор класса Person, выполняющий индексацию по этому полю. Вот как выглядят результаты работы теста.
(рис 19.5) Поверхностное клонированиеЕсли стандартное и реализовать метод Clone - единственный метод этого
Давайте расширим наш класс, придав ему родительский . Реализация метода Clone будет отличаться от стандартной реализации тем, что к имени объекта - полю Fam - будет приписываться слово "clone". Вот как выглядит этот метод:
public object Clone()
{
Person p = new Person(this.fam + "_clone");
//копирование полей
p.age = this.age; p.children = this.children;
p.count_children = this.count_children;
p.health = this.health; p.salary = this.salary;
p.status = this.status;
return (p);
}
Эта реализация является слегка модифицированной версией стандартного
Person mother_clone2 = (Person)mother.Clone();
Console.WriteLine("Дети клона_2: {0}",mother_clone2.Fam);
Console.WriteLine (mother_clone2[0].Fam);
Console.WriteLine (mother_clone2[1].Fam);
Все работает должным образом.
При работе с программной системой зачастую возникает необходимость в
Еще одно важное применение
Так же, как и
Если класс объявить с ISerializable, реализация методов которого позволит управлять процессом
Класс, объекты которого предполагается сериализовать стандартным образом, должен при объявлении сопровождаться [NonSerialized] - эти поля сохраняться не будут:
[Serializable]
public class Test
{
public string name;
[NonSerialized] int id;
int age;
//другие поля и методы класса
}
В класс Test встроен стандартный механизм id - нет.
Для запуска механизма необходимо создать объект, называемый форматером и выполняющий BinaryFormatter. Этот класс находится в пространстве имен библиотеки FCL:
System.Runtime.Serialization.Formatters.Binary
Давайте разберемся, как устроен этот класс. Он является наследником двух IFormatter и IRemotingFormatter. IFormatter имеет два открытых метода: Serialize и , позволяющих сохранять и восстанавливать всю совокупность связанных объектов с заданным объектом в качестве корня. IRemotingFormatter имеет те же открытые методы: Serialize и , позволяющие выполнять глубокую BinaryFormatter методы Serialize и перегружены. Для удаленного вызова задается дополнительный параметр, что и позволяет различать, локально или удаленно выполняются процессы обмена данными.
В пространстве имен библиотеки FCL:
System.Runtime.Serialization.Formatters.Soap
находится класс SoapFormatter. Он является наследником тех же IFormatter и IRemotingFormatter и реализует их методы Serialize и , позволяющие выполнять глубокую SoapFormatter, xml-сериализацию можно выполнять средствами другого класса -- XmlSerializer.
Из новых средств, еще не рассматривавшихся в наших лекциях, для организации IO библиотеки FCL предоставляет классы, поддерживающие ввод-вывод данных. В частности, в этом пространстве есть абстрактный класс Stream для работы с потоками данных. С одним из его потомков - классом FileStream - мы и будем работать в нашем примере.
В качестве примера промоделируем сказку Пушкина "О рыбаке и рыбке". Как вы помните, жадная старуха богатела, богатела, но после очередного желания оказалась у разбитого корыта, вернувшись в начальное состояние.
[Serializable]
public class Personage
{
public Personage(string name, int age)
{
this.name = name; this.age = age;
}
//поля класса
static int wishes;
public string name, status, wealth;
int age;
public Personage couple;
//методы класса
}
Герои сказки - объекты этого класса обладают свойствами, задающими имя, возраст, статус, имущество и супруга. Имя и возраст задаются в конструкторе класса, а остальные свойства задаются в следующем методе:
public void marry (Personage couple)
{
this.couple = couple;
couple.couple = this;
this.status ="крестьянин";
this.wealth ="рыбацкая сеть";
this.couple.status = "крестьянка";
this.couple.wealth = "корыто";
SaveState();
}
Предусловие метода предполагает, что метод вызывается один раз главным героем (рыбаком). В методе устанавливаются взаимные ссылки между героями сказки, их начальное состояние. Завершается метод сохранением состояния объектов, выполняемого при вызове метода SaveState:
void SaveState()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream
("State.bin",FileMode.Create, FileAccess.Write);
bf.Serialize(fs,this);
fs.Close();
}
Здесь и выполняется bf класса BinaryFormatter. Затем определяется файл, в котором будет сохраняться состояние объектов, - объект fs класса FileStream. Заметьте, в конструкторе файла, кроме имени файла, указываются его характеристики: статус, режим доступа. На деталях введения файлов я останавливаться не буду. Теперь, когда основные объекты определены, остается вызвать метод Serialize объекта bf, которому в качестве аргументов передается объект fs и текущий объект, представляющий корневой объект графа объектов, которые подлежат
Нам понадобится еще метод, описывающий жизнь героев сказки:
public Personage AskGoldFish()
{
Personage fisher = this;
if (fisher.name == "рыбак")
{
wishes++;
switch (wishes)
{
case 1: ChangeStateOne();break;
case 2: ChangeStateTwo();break;
case 3: ChangeStateThree();break;
default: BackState(ref fisher);break;
}
}
return(fisher);
}//AskGoldFish
Метод реализует анализ желаний героини сказки. Первые три желания исполняются, и состояние героев меняется:
void ChangeStateOne()
{
this.status = "муж дворянки";
this.couple.status = "дворянка";
this.couple.wealth = "имение";
}
void ChangeStateTwo()
{
this.status = "муж боярыни";
this.couple.status = "боярыня";
this.couple.wealth = "много поместий";
}
void ChangeStateThree()
{
this.status = "муж государыни";
this.couple.status = "государыня";
this.couple.wealth = "страна";
}
Начиная с четвертого желания, все возвращается в начальное состояние - выполняется
void BackState(ref Personage fisher)
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream
("State.bin",FileMode.Open, FileAccess.Read);
fisher = (Personage)bf.Deserialize(fs);
fs.Close();
}
Обратите внимание, что у метода есть аргумент, передаваемый по ссылке. Этот аргумент получает значение - ссылается на объект, создаваемый методом . Без аргумента метода не обойтись, поскольку возвращаемый методом объект нельзя присвоить текущему объекту this. Важно также отметить, что метод восстанавливает весь граф объектов, возвращая в качестве результата корень графа.
В классе определен еще один метод, сообщающий о текущем состоянии объектов:
public void About()
{
Console.WriteLine("имя = {0}, возраст = {1},"+
"статус = {2}, состояние ={3}",name,age,status, wealth);
Console.WriteLine("имя = {0}, возраст = {1}," +
"статус = {2}, состояние ={3}", this.couple.name,
this.couple.age,this.couple.status, this.couple.wealth);
}
Для завершения сказки нам нужно в клиентском классе создать ее героев:
public void TestGoldFish()
{
Personage fisher = new Personage("рыбак", 70);
Personage wife = new Personage("старуха", 70);
fisher.marry(wife);
Console.WriteLine("До золотой рыбки"); fisher.About();
fisher = fisher.AskGoldFish();
Console.WriteLine("Первое желание"); fisher.About();
fisher = fisher.AskGoldFish();
Console.WriteLine("Второе желание"); fisher.About();
fisher = fisher.AskGoldFish();
Console.WriteLine("Третье желание"); fisher.About();
fisher = fisher.AskGoldFish();
Console.WriteLine("Еще хочу"); fisher.About();
fisher = fisher.AskGoldFish();
Console.WriteLine("Хочу, но уже поздно"); fisher.About();
}
На рис. 19.6 показаны результаты исполнения сказки.
(рис 19.6) Сказка о рыбаке и рыбкеЧто изменится, если перейти к сохранению данных в xml-формате? немногое. Нужно лишь заменить объявление форматера:
void SaveStateXML()
{
SoapFormatter sf = new SoapFormatter();
FileStream fs = new FileStream
("State.xml",FileMode.Create, FileAccess.Write);
sf.Serialize(fs,this);
fs.Close();
}
void BackStateXML(ref Personage fisher)
{
SoapFormatter sf = new SoapFormatter();
FileStream fs = new FileStream
("State.xml",FileMode.Open, FileAccess.Read);
fisher = (Personage)sf.Deserialize(fs);
fs.Close();
}
Клиент, работающий с объектами класса, этих изменений и не почувствует. Результаты вычислений останутся теми же, что и в предыдущем случае. Правда, файл, сохраняющий данные, теперь выглядит совсем по-другому. Это обычный xml-документ, который мог быть создан в любом из приложений. Вот как выглядит этот документ, открытый в браузере Internet Explorer.
(рис 19.7) XML-документ, сохраняющий состояние объектов
При необходимости можно самому управлять процессом ISerializable. Класс, наследующий этот GetObjectData и добавить защищенный конструктор. Схема Serialize использует не стандартную реализацию, а вызывает метод GetObjectData, управляющий записью данных. Метод , в свою очередь, вызывает защищенный конструктор, создающий объект и заполняющий его поля сохраненными значениями.
Конечно, возможность управлять сохранением и восстановлением данных дает большую гибкость и позволяет, в конечном счете, уменьшить размер файла, хранящего данные, что может быть крайне важно, особенно если речь идет об обмене данными с удаленным приложением. Если речь идет о поверхностной NonSerialized, которым можно помечать поля, не требующие
Рассмотрим, как устроен метод GetObjectData, управляющий сохранением данных. У этого метода два аргумента:
GetObjectData(SerializedInfo info, StreamingContext context)
Поскольку самому вызывать этот метод не приходится - он вызывается автоматически методом Serialize, то можно не особенно задумываться о том, как создавать аргументы метода. Более важно понимать, как их следует использовать. Чаще всего используется только аргумент info и его метод AddValue (key, field). Данные сохраняются вместе с ключом, используемым позже при чтении данных. Аргумент key, который может быть произвольной строкой, задает ключ, а аргумент field - поле объекта. Например, для сохранения полей name и age можно задать следующие операторы:
info.AddValue("name",name); info.AddValue("age", age);
Поскольку имена полей уникальны, то их разумно использовать в качестве ключей.
Если поле son класса Father является объектом класса Child и этот класс сериализуем, то для сохранения объекта son следует вызвать метод:
son.GetObjectData(info, context)
Если не возникает циклов, причиной которых являются взаимные ссылки, то особых сложностей с
Перейдем теперь к рассмотрению специального конструктора класса. Он может быть объявлен с атрибутом доступа private, но лучше, как и во многих других случаях, применять атрибут protected, что позволит использовать этот конструктор потомками класса, осуществляющими собственную GetObjectData. Опять-таки, в основном используется аргумент info и его метод GetValue(key, type), который выполняет операцию, обратную к операции метода AddValue. По ключу key находится хранимое значение, а аргумент type позволяет привести его к нужному типу. У метода GetValue имеется множество типизированных версий, позволяющих не задавать тип. Так что восстановление полей name и age можно выполнить следующими операторами:
name = info.GetString("name"); age = info.GetInt32("age");
Восстановление поля son, являющегося ссылочным типом, выполняется вызовом его специального конструктора:
son = new Child(info, context);
А теперь вернемся к нашему примеру со стариком, старухой и золотой рыбкой. Заменим стандартную Personage, сделаем класс наследником ISerializable:
[Serializable]
public class Personage :ISerializable
{...}
Добавим в наш класс специальный метод, вызываемый при
//Специальный метод сериализации
public void GetObjectData(SerializationInfo info,
StreamingContext context)
{
info.AddValue("name",name); info.AddValue("age", age);
info.AddValue("status",status);
info.AddValue("wealth", wealth);
info.AddValue("couplename",couple.name);
info.AddValue("coupleage", couple.age);
info.AddValue("couplestatus",couple.status);
info.AddValue("couplewealth", couple.wealth);
}
В трех первых строках сохраняются значимые поля объекта и тут все ясно. Но вот запомнить поле, хранящее объект couple класса Personage, напрямую не удается. Попытка рекурсивного вызова
couple.GetObjectData(info,context);
привела бы к зацикливанию, если бы раньше из-за повторяющегося ключа не возникала исключительная ситуация в момент записи поля name объекта couple. Поэтому приходится явно сохранять поля этого объекта уже с другими ключами. Понятно, что с ростом сложности структуры графа объектов задача существенно осложняется.
Добавим в наш класс специальный конструктор, вызываемый при
//Специальный конструктор сериализации
protected Personage(SerializationInfo info,
StreamingContext context)
{
name = info.GetString("name"); age = info.GetInt32("age");
status = info.GetString("status");
wealth = info.GetString("wealth");
couple = new Personage(info.GetString("couplename"),
info.GetInt32("coupleage"));
couple.status = info.GetString("couplestatus");
couple.wealth = info.GetString("couplewealth");
this.couple = couple; couple.couple = this;
}
Опять первые строки восстановления значимых полей объекта прозрачно ясны. А с полем couple приходится повозиться. Вначале создается новый объект обычным конструктором, аргументы которого читаются из сохраняемой памяти. Затем восстанавливаются значения других полей этого объекта, а затем уже происходит взаимное связывание двух объектов.
Кроме введения конструктора класса и метода GetObjectData, никаких других изменений в проекте не понадобилось - ни в методах класса, ни на стороне клиента. Внешне проект работал совершенно идентично ситуации, когда не вводилось наследование Serialize и в процессе своей работы теперь вызывали созданный нами метод и конструктор класса. Небольшие изменения произошли и в файлах, хранящих данные.
Мораль: должны быть веские основания для отказа от стандартно реализованной
Когда в нашем примере вводилось собственное управление
| Формат | Сериализация | Размер файла |
|---|---|---|
| Бинарный поток | Стандартная | 355 байтов |
| Бинарный поток | Управляемая | 355 байтов |
| XML-документ | Стандартная | 1, 14 Кб. |
| XML-документ | Управляемая | 974 байта |
Преимуществами XML-документа являются его читабельность и хорошо развитые средства разбора, но зато бинарное представление выигрывает в объеме и скорости передачи тех же данных.
Слово "
Введение в язык частных случаев усложняет его и свидетельствует о некоторых изъянах, для преодоления которых и вводятся частные случаи. Например, введение структур в язык C# позволило определять классы как Подробнее о развернутых и ссылочных типах см. лекцию 17. |
Отметим одно важное IComparable, реализация которого позволяет сравнивать объекты не только на равенство, но и на "больше", "меньше".
Давайте опишем некоторый
public interface IProps
{
void Prop1(string s);
void Prop2 (string name, int val);
}
У этого
Класс, наследующий
public class Clain:IProps
{
public Clain() {}
public void Prop1(string s)
{
Console.WriteLine(s);
}
public void Prop2(string name, int val)
{
Console.WriteLine("name = {0}, val ={1}", name, val);
}
}//Clain
Класс реализует методы private, и уточняет имя метода именем
public class ClainP:IProps
{
public ClainP(){ }
void IProps.Prop1(string s)
{
Console.WriteLine(s);
}
void IProps.Prop2(string name, int val)
{
Console.WriteLine("name = {0}, val ={1}", name, val);
}
}//class ClainP
Класс ClainP реализовал методы IProps, но сделал их закрытыми и недоступными для вызова клиентами и наследниками класса. Как же получить доступ к закрытым методам? Есть два способа решения этой проблемы:
IProps, полученный преобразованием ( ClainP. Этому объекту доступны закрытые методы В чем главное достоинство ClainP:
public void MyProp1(string s)
{
((IProps)this).Prop1(s);
}
public void MyProp2(string s, int x)
{
((IProps)this).Prop2(s, x);
}
Как видите, методы переименованы и получили другие имена, под которыми они и будут известны клиентам класса. В обертке для вызова закрытого метода пришлось использовать this к интерфейсному классу IProps.
Создать объект класса new нельзя. Тем не менее, можно объявить объект Clain, ClainP, так и с объектами интерфейсного класса IProps:
public void TestClainIProps()
{
Console.WriteLine("Объект класса Clain вызывает
открытые методы!");
Clain clain = new Clain();
clain.Prop1(" свойство 1 объекта");
clain.Prop2("Владимир", 44);
Console.WriteLine("Объект класса IProps вызывает
открытые методы!");
IProps ip = (IProps)clain;
ip.Prop1("интерфейс: свойство");
ip.Prop2 ("интерфейс: свойство",77);
Console.WriteLine("Объект класса ClainP вызывает
открытые методы!");
ClainP clainp = new ClainP();
clainp.MyProp1(" свойство 1 объекта");
clainp.MyProp2("Владимир", 44);
Console.WriteLine("Объект класса IProps вызывает
закрытые методы!");
IProps ipp = (IProps)clainp;
ipp.Prop1("интерфейс: свойство");
ipp.Prop2 ("интерфейс: свойство",77);
}
Этот пример демонстрирует работу с классом, где все наследуемые методы
(рис 19.1) Наследование интерфейса. Две стратегии
При
Проблема
Стратегия
Другая стратегия исходит из того, что, несмотря на единую сигнатуру, методы разных
Итак,
public interface IProps
{
void Prop1(string s);
void Prop2 (string name, int val);
void Prop3();
}
public interface IPropsOne
{
void Prop1(string s);
void Prop2 (int val);
void Prop3();
}
У двух
public class ClainTwo:IProps,IPropsOne
{
/// <summary>
/// склеивание методов двух интерфейсов
/// </summary>
/// <param name="s"></param>
public void Prop1 (string s)
{
Console.WriteLine(s);
}
/// <summary>
/// перегрузка методов двух интерфейсов
/// </summary>
/// <param name="s"></param>
/// <param name="x"></param>
public void Prop2(string s, int x)
{
Console.WriteLine(s + "; " + x);
}
public void Prop2 (int x)
{
Console.WriteLine(x);
}
/// <summary>
/// переименование методов двух интерфейсов
/// </summary>
void IProps.Prop3()
{
Console.WriteLine("Свойство 3 интерфейса 1");
}
void IPropsOne.Prop3()
{
Console.WriteLine("Свойство 3 интерфейса 2");
}
public void Prop3FromInterface1()
{
((IProps)this).Prop3();
}
public void Prop3FromInterface2()
{
((IPropsOne)this).Prop3();
}
}
Для первого из методов с совпадающей сигнатурой выбрана стратегия
Приведу пример работы с объектами класса и интерфейсными объектами:
public void TestCliTwoInterfaces()
{
Console.WriteLine("Объект ClainTwo вызывает методы двух интерфейсов!");
Cli.ClainTwo claintwo = new Cli.ClainTwo();
claintwo.Prop1("Склейка свойства двух интерфейсов");
claintwo.Prop2("перегрузка ::: ",99);
claintwo.Prop2(9999);
claintwo.Prop3FromInterface1();
claintwo.Prop3FromInterface2();
Console.WriteLine("Интерфейсный объект вызывает методы 1-го
интерфейса!");
Cli.IProps ip1 = (Cli.IProps)claintwo;
ip1.Prop1("интерфейс IProps: свойство 1");
ip1.Prop2("интерфейс 1 ", 88);
ip1.Prop3();
Console.WriteLine("Интерфейсный объект вызывает методы 2-го
интерфейса!");
Cli.IPropsOne ip2 = (Cli.IPropsOne)claintwo;
ip2.Prop1("интерфейс IPropsOne: свойство1");
ip2.Prop2(7777);
ip2.Prop3();
}
Результаты работы тестирующей процедуры показаны на рис. 19.2.
(рис 19.2) Решение проблемы коллизии имен
Проблема C является наследником классов A и B, а те, в свой черед, являются наследниками класса Parent, то класс наследует свойства и методы своего предка Parent дважды, один раз получая их от класса A, другой от - B. Это явление называется еще дублирующим наследованием. Для классов ситуация осложняется тем, что классы A и B могли по-разному переопределить методы родителя и для потомков предстоит сложный выбор реализации.
Для
Начнем наш пример с наследования
public interface IParent
{
void ParentMethod();
}
public interface ISon1:IParent
{
void Son1Method();
}
public interface ISon2:IParent
{
void Son2Method();
}
Два сыновних
public class Pars:ISon1, ISon2
{
public void ParentMethod()
{
Console.WriteLine("Это метод родителя!");
}
public void Son1Method()
{
Console.WriteLine("Это метод старшего сына!");
}
public void Son2Method()
{
Console.WriteLine("Это метод младшего сына!");
}
}//class Pars
Класс обязан реализовать метод ParentMethod, приходящий от обоих IParent, лучшей стратегией реализации является
public void TestIParsons()
{
Console.WriteLine("Объект класса вызывает методы трех
интерфейсов!");
Cli.Pars ct = new Cli.Pars();
ct.ParentMethod();
ct.Son1Method();
ct.Son2Method();
Console.WriteLine("Интерфейсный объект 1 вызывает свои
методы!");
Cli.IParent ip = (IParent)ct;
ip.ParentMethod();
Console.WriteLine("Интерфейсный объект 2 вызывает свои
методы!");
Cli.ISon1 ip1 = (ISon1)ct;
ip1.ParentMethod();
ip1.Son1Method();
Console.WriteLine("Интерфейсный объект 3 вызывает свои
методы!");
Cli.ISon2 ip2 = (ISon2)ct;
ip2.ParentMethod();
ip2.Son2Method();
}
Результаты работы тестирующей процедуры показаны на рис. 19.3.
(рис 19.3) Дублирующее наследование интерфейсов
Рассмотрим несколько
Часто, когда создается класс, желательно задать IComparable. Этот CompareTo (object obj), возвращающий целочисленное значение, положительное, отрицательное или равное нулю, в зависимости от выполнения отношения "больше", "меньше" или "равно".
Как правило, в классе вначале определяют метод CompareTo, а после этого вводят перегруженные операции, чтобы выполнять сравнение объектов привычным образом с использованием знаков операций отношения.
Давайте введем Person, рассмотренном в лекции 16, сделав этот класс наследником IComparable. Реализуем в этом классе метод CompareTo:
public class Person:IComparable
{
public int CompareTo( object pers)
{
const string s = "Сравниваемый объект не принадлежит классу Person";
Person p = pers as Person;
if (!p.Equals(null))
return (fam.CompareTo(p.fam));
throw new ArgumentException (s);
}
// другие компоненты класса
}
Поскольку аргумент метода должен иметь универсальный тип object, то перед выполнением сравнения его нужно привести к типу Person. Это as, позволяющую проверить корректность выполнения
При is и as. Логическое выражение (obj is T) истинно, если объект obj имеет тип T. Оператор присваивания ( obj = P as T; ) присваивает объекту obj объект P, приведенный к типу T, если такое null. Семантику as можно выразить следующим условным выражением: (P is T) ? (T)P : (T)null.
Заметьте также, что при проверке на значение null используется отношение Equals, а не обычное равенство, которое будет переопределено.
Person задается как отношение порядка на фамилиях персон. Так как строки наследуют IComparable, то для фамилий персон вызывается метод CompareTo, его результат и возвращается в качестве результата метода CompareTo для персон. Если аргумент метода не будет соответствовать нужному типу, то выбрасывается исключение со специальным уведомлением.
Конечно, сравнение персон может выполняться по разным критериям: возрасту, росту, зарплате. Общий подход к сравнению персон будет рассмотрен в следующей лекции 20.
Введем теперь в нашем классе Person
public static bool operator <(Person p1, Person p2)
{
return (p1.CompareTo(p2) < 0);
}
public static bool operator >(Person p1, Person p2)
{
return (p1.CompareTo(p2) > 0);
}
public static bool operator <=(Person p1, Person p2)
{
return (p1.CompareTo(p2) <= 0);
}
public static bool operator >=(Person p1, Person p2)
{
return (p1.CompareTo(p2) >=0);
}
public static bool operator ==(Person p1, Person p2)
{
return (p1.CompareTo(p2) == 0);
}
public static bool operator !=(Person p1, Person p2)
{
return (p1.CompareTo(p2) != 0);
}
Как обычно, приведу тестовый пример, проверяющий работу с введенными методами:
public void TestCompare()
{
Person poet1 = new Person("Пушкин");
Person poet2 = new Person("Лермонтов");
Person poet3 = new Person("Пастернак");
Person poet4 = new Person("Мандельштам");
Person poet5 = new Person("Ахматова");
Person poet6 = new Person("Цветаева");
Console.WriteLine("{0} > {1} = {2}", poet1.Fam,
poet2.Fam, (poet1 > poet2));
Console.WriteLine("{0} >= {1} = {2}", poet3.Fam,
poet4.Fam, (poet3 >= poet4));
Console.WriteLine("{0} != {1} = {2}", poet5.Fam,
poet6.Fam, (poet5 != poet6));
}
Вот результаты работы этого теста.
(рис 19.4) Сравнение персонКонечно, заданный нами порядок не имеет никакого отношения к поэтическому дару, а лишь говорит об относительном расположении фамилий поэтов в словарях.
MemberwiseClone, наследуемого от прародителя object. Единственное, что нужно помнить: этот метод защищен, он не может быть вызван у клиента класса. Поэтому
Давайте обеспечим эту возможность для класса Person, создав в нем соответствующий метод:
public Person StandartClone()
{
Person p = (Person)this.MemberwiseClone();
return(p);
}
Теперь клиенты класса могут легко создавать поверхностные
public void TestStandartClone()
{
Person mother = new Person("Петрова Анна");
Person daughter = new Person("Петрова Ольга");
Person son = new Person("Петров Игорь");
mother[0] = daughter;
mother[1] = son;
Person mother_clone = mother.StandartClone();
Console.WriteLine("Дети матери: {0}",mother.Fam);
Console.WriteLine (mother[0].Fam);
Console.WriteLine (mother[1].Fam);
Console.WriteLine("Дети клона: {0}",mother_clone.Fam);
Console.WriteLine (mother_clone[0].Fam);
Console.WriteLine (mother_clone[1].Fam);
}
При создании mother. Обратите внимание: при работе с полем children, задающим детей, используется индексатор класса Person, выполняющий индексацию по этому полю. Вот как выглядят результаты работы теста.
(рис 19.5) Поверхностное клонированиеЕсли стандартное и реализовать метод Clone - единственный метод этого
Давайте расширим наш класс, придав ему родительский . Реализация метода Clone будет отличаться от стандартной реализации тем, что к имени объекта - полю Fam - будет приписываться слово "clone". Вот как выглядит этот метод:
public object Clone()
{
Person p = new Person(this.fam + "_clone");
//копирование полей
p.age = this.age; p.children = this.children;
p.count_children = this.count_children;
p.health = this.health; p.salary = this.salary;
p.status = this.status;
return (p);
}
Эта реализация является слегка модифицированной версией стандартного
Person mother_clone2 = (Person)mother.Clone();
Console.WriteLine("Дети клона_2: {0}",mother_clone2.Fam);
Console.WriteLine (mother_clone2[0].Fam);
Console.WriteLine (mother_clone2[1].Fam);
Все работает должным образом.
При работе с программной системой зачастую возникает необходимость в
Еще одно важное применение
Так же, как и
Если класс объявить с ISerializable, реализация методов которого позволит управлять процессом
Класс, объекты которого предполагается сериализовать стандартным образом, должен при объявлении сопровождаться [NonSerialized] - эти поля сохраняться не будут:
[Serializable]
public class Test
{
public string name;
[NonSerialized] int id;
int age;
//другие поля и методы класса
}
В класс Test встроен стандартный механизм id - нет.
Для запуска механизма необходимо создать объект, называемый форматером и выполняющий BinaryFormatter. Этот класс находится в пространстве имен библиотеки FCL:
System.Runtime.Serialization.Formatters.Binary
Давайте разберемся, как устроен этот класс. Он является наследником двух IFormatter и IRemotingFormatter. IFormatter имеет два открытых метода: Serialize и , позволяющих сохранять и восстанавливать всю совокупность связанных объектов с заданным объектом в качестве корня. IRemotingFormatter имеет те же открытые методы: Serialize и , позволяющие выполнять глубокую BinaryFormatter методы Serialize и перегружены. Для удаленного вызова задается дополнительный параметр, что и позволяет различать, локально или удаленно выполняются процессы обмена данными.
В пространстве имен библиотеки FCL:
System.Runtime.Serialization.Formatters.Soap
находится класс SoapFormatter. Он является наследником тех же IFormatter и IRemotingFormatter и реализует их методы Serialize и , позволяющие выполнять глубокую SoapFormatter, xml-сериализацию можно выполнять средствами другого класса -- XmlSerializer.
Из новых средств, еще не рассматривавшихся в наших лекциях, для организации IO библиотеки FCL предоставляет классы, поддерживающие ввод-вывод данных. В частности, в этом пространстве есть абстрактный класс Stream для работы с потоками данных. С одним из его потомков - классом FileStream - мы и будем работать в нашем примере.
В качестве примера промоделируем сказку Пушкина "О рыбаке и рыбке". Как вы помните, жадная старуха богатела, богатела, но после очередного желания оказалась у разбитого корыта, вернувшись в начальное состояние.
[Serializable]
public class Personage
{
public Personage(string name, int age)
{
this.name = name; this.age = age;
}
//поля класса
static int wishes;
public string name, status, wealth;
int age;
public Personage couple;
//методы класса
}
Герои сказки - объекты этого класса обладают свойствами, задающими имя, возраст, статус, имущество и супруга. Имя и возраст задаются в конструкторе класса, а остальные свойства задаются в следующем методе:
public void marry (Personage couple)
{
this.couple = couple;
couple.couple = this;
this.status ="крестьянин";
this.wealth ="рыбацкая сеть";
this.couple.status = "крестьянка";
this.couple.wealth = "корыто";
SaveState();
}
Предусловие метода предполагает, что метод вызывается один раз главным героем (рыбаком). В методе устанавливаются взаимные ссылки между героями сказки, их начальное состояние. Завершается метод сохранением состояния объектов, выполняемого при вызове метода SaveState:
void SaveState()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream
("State.bin",FileMode.Create, FileAccess.Write);
bf.Serialize(fs,this);
fs.Close();
}
Здесь и выполняется bf класса BinaryFormatter. Затем определяется файл, в котором будет сохраняться состояние объектов, - объект fs класса FileStream. Заметьте, в конструкторе файла, кроме имени файла, указываются его характеристики: статус, режим доступа. На деталях введения файлов я останавливаться не буду. Теперь, когда основные объекты определены, остается вызвать метод Serialize объекта bf, которому в качестве аргументов передается объект fs и текущий объект, представляющий корневой объект графа объектов, которые подлежат
Нам понадобится еще метод, описывающий жизнь героев сказки:
public Personage AskGoldFish()
{
Personage fisher = this;
if (fisher.name == "рыбак")
{
wishes++;
switch (wishes)
{
case 1: ChangeStateOne();break;
case 2: ChangeStateTwo();break;
case 3: ChangeStateThree();break;
default: BackState(ref fisher);break;
}
}
return(fisher);
}//AskGoldFish
Метод реализует анализ желаний героини сказки. Первые три желания исполняются, и состояние героев меняется:
void ChangeStateOne()
{
this.status = "муж дворянки";
this.couple.status = "дворянка";
this.couple.wealth = "имение";
}
void ChangeStateTwo()
{
this.status = "муж боярыни";
this.couple.status = "боярыня";
this.couple.wealth = "много поместий";
}
void ChangeStateThree()
{
this.status = "муж государыни";
this.couple.status = "государыня";
this.couple.wealth = "страна";
}
Начиная с четвертого желания, все возвращается в начальное состояние - выполняется
void BackState(ref Personage fisher)
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream
("State.bin",FileMode.Open, FileAccess.Read);
fisher = (Personage)bf.Deserialize(fs);
fs.Close();
}
Обратите внимание, что у метода есть аргумент, передаваемый по ссылке. Этот аргумент получает значение - ссылается на объект, создаваемый методом . Без аргумента метода не обойтись, поскольку возвращаемый методом объект нельзя присвоить текущему объекту this. Важно также отметить, что метод восстанавливает весь граф объектов, возвращая в качестве результата корень графа.
В классе определен еще один метод, сообщающий о текущем состоянии объектов:
public void About()
{
Console.WriteLine("имя = {0}, возраст = {1},"+
"статус = {2}, состояние ={3}",name,age,status, wealth);
Console.WriteLine("имя = {0}, возраст = {1}," +
"статус = {2}, состояние ={3}", this.couple.name,
this.couple.age,this.couple.status, this.couple.wealth);
}
Для завершения сказки нам нужно в клиентском классе создать ее героев:
public void TestGoldFish()
{
Personage fisher = new Personage("рыбак", 70);
Personage wife = new Personage("старуха", 70);
fisher.marry(wife);
Console.WriteLine("До золотой рыбки"); fisher.About();
fisher = fisher.AskGoldFish();
Console.WriteLine("Первое желание"); fisher.About();
fisher = fisher.AskGoldFish();
Console.WriteLine("Второе желание"); fisher.About();
fisher = fisher.AskGoldFish();
Console.WriteLine("Третье желание"); fisher.About();
fisher = fisher.AskGoldFish();
Console.WriteLine("Еще хочу"); fisher.About();
fisher = fisher.AskGoldFish();
Console.WriteLine("Хочу, но уже поздно"); fisher.About();
}
На рис. 19.6 показаны результаты исполнения сказки.
(рис 19.6) Сказка о рыбаке и рыбкеЧто изменится, если перейти к сохранению данных в xml-формате? немногое. Нужно лишь заменить объявление форматера:
void SaveStateXML()
{
SoapFormatter sf = new SoapFormatter();
FileStream fs = new FileStream
("State.xml",FileMode.Create, FileAccess.Write);
sf.Serialize(fs,this);
fs.Close();
}
void BackStateXML(ref Personage fisher)
{
SoapFormatter sf = new SoapFormatter();
FileStream fs = new FileStream
("State.xml",FileMode.Open, FileAccess.Read);
fisher = (Personage)sf.Deserialize(fs);
fs.Close();
}
Клиент, работающий с объектами класса, этих изменений и не почувствует. Результаты вычислений останутся теми же, что и в предыдущем случае. Правда, файл, сохраняющий данные, теперь выглядит совсем по-другому. Это обычный xml-документ, который мог быть создан в любом из приложений. Вот как выглядит этот документ, открытый в браузере Internet Explorer.
(рис 19.7) XML-документ, сохраняющий состояние объектов
При необходимости можно самому управлять процессом ISerializable. Класс, наследующий этот GetObjectData и добавить защищенный конструктор. Схема Serialize использует не стандартную реализацию, а вызывает метод GetObjectData, управляющий записью данных. Метод , в свою очередь, вызывает защищенный конструктор, создающий объект и заполняющий его поля сохраненными значениями.
Конечно, возможность управлять сохранением и восстановлением данных дает большую гибкость и позволяет, в конечном счете, уменьшить размер файла, хранящего данные, что может быть крайне важно, особенно если речь идет об обмене данными с удаленным приложением. Если речь идет о поверхностной NonSerialized, которым можно помечать поля, не требующие
Рассмотрим, как устроен метод GetObjectData, управляющий сохранением данных. У этого метода два аргумента:
GetObjectData(SerializedInfo info, StreamingContext context)
Поскольку самому вызывать этот метод не приходится - он вызывается автоматически методом Serialize, то можно не особенно задумываться о том, как создавать аргументы метода. Более важно понимать, как их следует использовать. Чаще всего используется только аргумент info и его метод AddValue (key, field). Данные сохраняются вместе с ключом, используемым позже при чтении данных. Аргумент key, который может быть произвольной строкой, задает ключ, а аргумент field - поле объекта. Например, для сохранения полей name и age можно задать следующие операторы:
info.AddValue("name",name); info.AddValue("age", age);
Поскольку имена полей уникальны, то их разумно использовать в качестве ключей.
Если поле son класса Father является объектом класса Child и этот класс сериализуем, то для сохранения объекта son следует вызвать метод:
son.GetObjectData(info, context)
Если не возникает циклов, причиной которых являются взаимные ссылки, то особых сложностей с
Перейдем теперь к рассмотрению специального конструктора класса. Он может быть объявлен с атрибутом доступа private, но лучше, как и во многих других случаях, применять атрибут protected, что позволит использовать этот конструктор потомками класса, осуществляющими собственную GetObjectData. Опять-таки, в основном используется аргумент info и его метод GetValue(key, type), который выполняет операцию, обратную к операции метода AddValue. По ключу key находится хранимое значение, а аргумент type позволяет привести его к нужному типу. У метода GetValue имеется множество типизированных версий, позволяющих не задавать тип. Так что восстановление полей name и age можно выполнить следующими операторами:
name = info.GetString("name"); age = info.GetInt32("age");
Восстановление поля son, являющегося ссылочным типом, выполняется вызовом его специального конструктора:
son = new Child(info, context);
А теперь вернемся к нашему примеру со стариком, старухой и золотой рыбкой. Заменим стандартную Personage, сделаем класс наследником ISerializable:
[Serializable]
public class Personage :ISerializable
{...}
Добавим в наш класс специальный метод, вызываемый при
//Специальный метод сериализации
public void GetObjectData(SerializationInfo info,
StreamingContext context)
{
info.AddValue("name",name); info.AddValue("age", age);
info.AddValue("status",status);
info.AddValue("wealth", wealth);
info.AddValue("couplename",couple.name);
info.AddValue("coupleage", couple.age);
info.AddValue("couplestatus",couple.status);
info.AddValue("couplewealth", couple.wealth);
}
В трех первых строках сохраняются значимые поля объекта и тут все ясно. Но вот запомнить поле, хранящее объект couple класса Personage, напрямую не удается. Попытка рекурсивного вызова
couple.GetObjectData(info,context);
привела бы к зацикливанию, если бы раньше из-за повторяющегося ключа не возникала исключительная ситуация в момент записи поля name объекта couple. Поэтому приходится явно сохранять поля этого объекта уже с другими ключами. Понятно, что с ростом сложности структуры графа объектов задача существенно осложняется.
Добавим в наш класс специальный конструктор, вызываемый при
//Специальный конструктор сериализации
protected Personage(SerializationInfo info,
StreamingContext context)
{
name = info.GetString("name"); age = info.GetInt32("age");
status = info.GetString("status");
wealth = info.GetString("wealth");
couple = new Personage(info.GetString("couplename"),
info.GetInt32("coupleage"));
couple.status = info.GetString("couplestatus");
couple.wealth = info.GetString("couplewealth");
this.couple = couple; couple.couple = this;
}
Опять первые строки восстановления значимых полей объекта прозрачно ясны. А с полем couple приходится повозиться. Вначале создается новый объект обычным конструктором, аргументы которого читаются из сохраняемой памяти. Затем восстанавливаются значения других полей этого объекта, а затем уже происходит взаимное связывание двух объектов.
Кроме введения конструктора класса и метода GetObjectData, никаких других изменений в проекте не понадобилось - ни в методах класса, ни на стороне клиента. Внешне проект работал совершенно идентично ситуации, когда не вводилось наследование Serialize и в процессе своей работы теперь вызывали созданный нами метод и конструктор класса. Небольшие изменения произошли и в файлах, хранящих данные.
Мораль: должны быть веские основания для отказа от стандартно реализованной
Когда в нашем примере вводилось собственное управление
| Формат | Сериализация | Размер файла |
|---|---|---|
| Бинарный поток | Стандартная | 355 байтов |
| Бинарный поток | Управляемая | 355 байтов |
| XML-документ | Стандартная | 1, 14 Кб. |
| XML-документ | Управляемая | 974 байта |
Преимуществами XML-документа являются его читабельность и хорошо развитые средства разбора, но зато бинарное представление выигрывает в объеме и скорости передачи тех же данных.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.