トップ回答者
XmlSerializer の デシリアライズ時の 親クラスの参照追加

質問
-
階層構造になったクラスを XmlSerializer で シリアライズして、再度デシリアライズしています。
その際、下記のクラスですと、子クラスのParent の参照が復元できません。
再度デシリアライズ時に、Add のメソッドが使用(呼び出)されていませんでした。一旦、別のクラスを作成して Foreach 等で ループしながら 復元したいクラスへプロパティ毎にコピーすれば可能ですが、
もう少しスマートな方法はないでしょう?public class 検査条件 { public string 検査名; public float 判定値; public ObservableCollection<検査点> 検査点一覧 = new ObservableCollection<検査点>(); public void Add(検査点 obj) { obj.Parent = this; 検査点一覧.Add(obj); } public class 検査点 { [XmlIgnore] public 検査条件 Parent; public string 測定点; public float 測定値; public bool 合格 { get { bool ret = false; if (測定値 >= Parent.判定値) { ret = true; } return ret; } } } } // INotifyPropertyChangedインターフェースの実装は省略しています。
回答
-
ObservableCollectionのコレクション変更イベントで親を設定するとか。
class 検査条件 { pubilc ObservableCollection<検査点> 検査点一覧 { get; set; } = new ObservableCollection<検査点>(); public 検査条件() { this.検査点一覧.CollectionChanged += this.On検査点一覧Changed; } private void On検査点一覧Changed(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (検査点 p in e.OldItems) p.Parent = null; } if (e.NewItems != null) { foreach (検査点 p in e.NewItems) p.Parent = this; } } }
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年10月2日 6:05
- 回答としてマーク Watanabe NS 2017年10月4日 10:18
-
こんにちは。
検査条件クラスにICollection<T>インターフェイスを実装するという方法はどうでしょうか。
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; namespace ConsoleApp1 { public class Program { public static void Main(string[] args) { var 検査条件 = new 検査条件(); 検査条件.Add(new 検査点() { 測定値 = 1 }); 検査条件.Add(new 検査点() { 測定値 = 2 }); 検査条件.Add(new 検査点() { 測定値 = 3 }); var serializer = new XmlSerializer(typeof(検査条件)); using (var stream = new MemoryStream()) { serializer.Serialize(stream, 検査条件); stream.Position = 0; 検査条件 = (検査条件)serializer.Deserialize(stream); } } } public class 検査条件 : ICollection<検査点> { public string 検査名; public float 判定値; public ObservableCollection<検査点> 検査点一覧 = new ObservableCollection<検査点>(); public int Count => 検査点一覧.Count; public bool IsReadOnly => false; public void Add(検査点 item) { item.Parent = this; 検査点一覧.Add(item); } public void Clear() { 検査点一覧.Clear(); } public bool Contains(検査点 item) { return 検査点一覧.Contains(item); } public void CopyTo(検査点[] array, int arrayIndex) { 検査点一覧.CopyTo(array, arrayIndex); } public IEnumerator<検査点> GetEnumerator() { return 検査点一覧.GetEnumerator(); } public bool Remove(検査点 item) { return 検査点一覧.Remove(item); } IEnumerator IEnumerable.GetEnumerator() { return 検査点一覧.GetEnumerator(); } } public class 検査点 { [XmlIgnore] public 検査条件 Parent; public string 測定点; public float 測定値; public bool 合格 { get { bool ret = false; if (測定値 >= Parent.判定値) { ret = true; } return ret; } } } }
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年10月2日 6:05
- 回答としてマーク Watanabe NS 2017年10月4日 10:18
-
いろいろ手抜きできるObservableCollectionを作ってみるとか
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Xml.Serialization; namespace ConsoleApplication1 { public class Program { static void Main(string[] args) { 検査条件 data1 = CreateTest(1); 検査条件 data2 = CreateTest(2); //親を自動的に変更してみるテスト data1.検査点一覧.Add(data2.検査点一覧[3]); data2.検査点一覧.Add(data1.検査点一覧[5]); SerializeTest(data1); SerializeTest(data2); Console.ReadLine(); } private static 検査条件 CreateTest(int id) { var data = new 検査条件(); data.検査名 = "親" + id; for (int i = 0; i < 10; i++) { data.検査点一覧.Add(new 検査条件.検査点() { 測定値 = i, 測定点 = id + "-" + i.ToString() }); } return data; } private static void SerializeTest(検査条件 data) { System.Xml.Serialization.XmlSerializer seri = new XmlSerializer(typeof(検査条件)); System.IO.MemoryStream ms = new System.IO.MemoryStream(); seri.Serialize(ms, data); ms.Position = 0; Console.WriteLine(data.検査名); var dataX = seri.Deserialize(ms) as 検査条件; foreach (検査条件.検査点 item in dataX.検査点一覧) { Console.WriteLine("\t" + item.測定点 + "\t" + (item.Parent == null ? "NULL" : item.Parent.検査名)); } } public class 検査条件 : IChildren<検査条件.検査点> { public 検査条件() { //親を自動設定するObservableColelction 検査点一覧 = new ObservableCollection<検査点, 検査条件>(this); } public string 検査名; public float 判定値; public ObservableCollection<検査点> 検査点一覧 { get; private set; } ICollection<検査条件.検査点> IChildren<検査条件.検査点>.Children { get { return 検査点一覧; } } //ObservableCollectionで自動処理するので要らない //public void Add(検査点 obj) //{ // obj.Parent = this; // 検査点一覧.Add(obj); //} public class 検査点 : IParent<検査条件> { [XmlIgnore] public 検査条件 Parent { get; set; } public string 測定点; public float 測定値; public bool 合格 { get { bool ret = false; if (測定値 >= Parent.判定値) { ret = true; } return ret; } } } } } interface IParent { } interface IParent<TParent> : IParent where TParent : class { TParent Parent { get; set; } } interface IChildren { } interface IChildren<TChild> where TChild : class { ICollection<TChild> Children { get; } } /// <summary>親子関係を自動適用するObservablecollection</summary> /// <typeparam name="TChild">子とするクラスの型</typeparam> /// <typeparam name="TParent">親とするクラスの型</typeparam> class ObservableCollection<TChild, TParent> : ObservableCollection<TChild> where TChild : class,IParent<TParent> where TParent : class { public ObservableCollection(TParent parent) { this.Parent = parent; } public TParent Parent { get { return _Parent; } set { this._Parent = value; foreach (TChild child in this) { child.Parent = value; } } } private TParent _Parent; protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (TChild child in e.OldItems) { child.Parent = null; } } if (e.NewItems != null) { foreach (TChild child in e.NewItems) { var ic = child.Parent as IChildren<TChild>; if (ic != null) { //別の親に移動したら元の親子から取り除く ic.Children.Remove(child); } child.Parent = this.Parent; } } base.OnCollectionChanged(e); } } }
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 編集済み gekkaMVP 2017年9月28日 8:51 コードの貼り間違い
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年10月2日 6:05
- 回答としてマーク Watanabe NS 2017年10月4日 10:18
すべての返信
-
こんにちは。
検査条件クラスにICollection<T>インターフェイスを実装するという方法はどうでしょうか。
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; namespace ConsoleApp1 { public class Program { public static void Main(string[] args) { var 検査条件 = new 検査条件(); 検査条件.Add(new 検査点() { 測定値 = 1 }); 検査条件.Add(new 検査点() { 測定値 = 2 }); 検査条件.Add(new 検査点() { 測定値 = 3 }); var serializer = new XmlSerializer(typeof(検査条件)); using (var stream = new MemoryStream()) { serializer.Serialize(stream, 検査条件); stream.Position = 0; 検査条件 = (検査条件)serializer.Deserialize(stream); } } } public class 検査条件 : ICollection<検査点> { public string 検査名; public float 判定値; public ObservableCollection<検査点> 検査点一覧 = new ObservableCollection<検査点>(); public int Count => 検査点一覧.Count; public bool IsReadOnly => false; public void Add(検査点 item) { item.Parent = this; 検査点一覧.Add(item); } public void Clear() { 検査点一覧.Clear(); } public bool Contains(検査点 item) { return 検査点一覧.Contains(item); } public void CopyTo(検査点[] array, int arrayIndex) { 検査点一覧.CopyTo(array, arrayIndex); } public IEnumerator<検査点> GetEnumerator() { return 検査点一覧.GetEnumerator(); } public bool Remove(検査点 item) { return 検査点一覧.Remove(item); } IEnumerator IEnumerable.GetEnumerator() { return 検査点一覧.GetEnumerator(); } } public class 検査点 { [XmlIgnore] public 検査条件 Parent; public string 測定点; public float 測定値; public bool 合格 { get { bool ret = false; if (測定値 >= Parent.判定値) { ret = true; } return ret; } } } }
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年10月2日 6:05
- 回答としてマーク Watanabe NS 2017年10月4日 10:18
-
ObservableCollectionのコレクション変更イベントで親を設定するとか。
class 検査条件 { pubilc ObservableCollection<検査点> 検査点一覧 { get; set; } = new ObservableCollection<検査点>(); public 検査条件() { this.検査点一覧.CollectionChanged += this.On検査点一覧Changed; } private void On検査点一覧Changed(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (検査点 p in e.OldItems) p.Parent = null; } if (e.NewItems != null) { foreach (検査点 p in e.NewItems) p.Parent = this; } } }
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年10月2日 6:05
- 回答としてマーク Watanabe NS 2017年10月4日 10:18
-
いろいろ手抜きできるObservableCollectionを作ってみるとか
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Xml.Serialization; namespace ConsoleApplication1 { public class Program { static void Main(string[] args) { 検査条件 data1 = CreateTest(1); 検査条件 data2 = CreateTest(2); //親を自動的に変更してみるテスト data1.検査点一覧.Add(data2.検査点一覧[3]); data2.検査点一覧.Add(data1.検査点一覧[5]); SerializeTest(data1); SerializeTest(data2); Console.ReadLine(); } private static 検査条件 CreateTest(int id) { var data = new 検査条件(); data.検査名 = "親" + id; for (int i = 0; i < 10; i++) { data.検査点一覧.Add(new 検査条件.検査点() { 測定値 = i, 測定点 = id + "-" + i.ToString() }); } return data; } private static void SerializeTest(検査条件 data) { System.Xml.Serialization.XmlSerializer seri = new XmlSerializer(typeof(検査条件)); System.IO.MemoryStream ms = new System.IO.MemoryStream(); seri.Serialize(ms, data); ms.Position = 0; Console.WriteLine(data.検査名); var dataX = seri.Deserialize(ms) as 検査条件; foreach (検査条件.検査点 item in dataX.検査点一覧) { Console.WriteLine("\t" + item.測定点 + "\t" + (item.Parent == null ? "NULL" : item.Parent.検査名)); } } public class 検査条件 : IChildren<検査条件.検査点> { public 検査条件() { //親を自動設定するObservableColelction 検査点一覧 = new ObservableCollection<検査点, 検査条件>(this); } public string 検査名; public float 判定値; public ObservableCollection<検査点> 検査点一覧 { get; private set; } ICollection<検査条件.検査点> IChildren<検査条件.検査点>.Children { get { return 検査点一覧; } } //ObservableCollectionで自動処理するので要らない //public void Add(検査点 obj) //{ // obj.Parent = this; // 検査点一覧.Add(obj); //} public class 検査点 : IParent<検査条件> { [XmlIgnore] public 検査条件 Parent { get; set; } public string 測定点; public float 測定値; public bool 合格 { get { bool ret = false; if (測定値 >= Parent.判定値) { ret = true; } return ret; } } } } } interface IParent { } interface IParent<TParent> : IParent where TParent : class { TParent Parent { get; set; } } interface IChildren { } interface IChildren<TChild> where TChild : class { ICollection<TChild> Children { get; } } /// <summary>親子関係を自動適用するObservablecollection</summary> /// <typeparam name="TChild">子とするクラスの型</typeparam> /// <typeparam name="TParent">親とするクラスの型</typeparam> class ObservableCollection<TChild, TParent> : ObservableCollection<TChild> where TChild : class,IParent<TParent> where TParent : class { public ObservableCollection(TParent parent) { this.Parent = parent; } public TParent Parent { get { return _Parent; } set { this._Parent = value; foreach (TChild child in this) { child.Parent = value; } } } private TParent _Parent; protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (TChild child in e.OldItems) { child.Parent = null; } } if (e.NewItems != null) { foreach (TChild child in e.NewItems) { var ic = child.Parent as IChildren<TChild>; if (ic != null) { //別の親に移動したら元の親子から取り除く ic.Children.Remove(child); } child.Parent = this.Parent; } } base.OnCollectionChanged(e); } } }
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 編集済み gekkaMVP 2017年9月28日 8:51 コードの貼り間違い
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年10月2日 6:05
- 回答としてマーク Watanabe NS 2017年10月4日 10:18
-
ご回答を頂きありがとうございます。
お礼が遅くなり申し訳ありませんでした。標準的な機能は無いという事が分かったのが収穫です。
”出来ない事”を調べるのは難しいです。頂いた回答ではC#初心者(VB経験者)にとっては、
Hongliang様のイベント処理が分かり易くコードもすぐに理解できました。
今回はこの方法で進めていこうと思います。将来的にはTakashi Shinohara様やgekka様のコードも書けるようになりたいです。
その為にはインターフェースの理解をしないといけないですね。特にUIをXAMLで書いていますので、
gekka様のObservableCollection を用意するのも良いですね。今後も機会がありましたらよろしくお願いします。