C#の内部セッターでプロパティをシリアル化する方法はありますか?
これは問題があるかもしれないことを理解していますが、方法があれば知りたいのですが。
例:
[Serializable]
public class Person
{
public int ID { get; internal set; }
public string Name { get; set; }
public int Age { get; set; }
}
クラスのインスタンスをシリアル化するコードPerson:
Person person = new Person();
person.Age = 27;
person.Name = "Patrik";
person.ID = 1;
XmlSerializer serializer = new XmlSerializer(typeof(Person));
TextWriter writer = new StreamWriter(@"c:\test.xml");
serializer.Serialize(writer, person);
writer.Close();
結果(IDプロパティがありません):
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Patrik</Name>
<Age>27</Age>
</Person>
オプションの場合、 DataContractSerializer
(。NET 3.0)は非公開プロパティをシリアル化できます。
[DataContract]
public class Person
{
[DataMember]
public int ID { get; internal set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
}
...
static void Main()
{
Person person = new Person();
person.Age = 27;
person.Name = "Patrik";
person.ID = 1;
DataContractSerializer serializer = new DataContractSerializer(typeof(Person));
XmlWriter writer = XmlWriter.Create(@"c:\test.xml");
serializer.WriteObject(writer, person);
writer.Close();
}
Xmlを使用すると(再フォーマット):
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/">
<Age>27</Age>
<ID>1</ID>
<Name>Patrik</Name>
</Person>
IXmlSerializable を実装できますが、残念ながら、これはXmlSerializerの最も重要な利点(シリアル化を宣言的に制御する機能)を無効にします。 DataContractSerializer (xmlベース)および BinaryFormatter (バイナリベース)は、それぞれ長所と短所があるXmlSerializerの代替として使用できます。
私はそう思います 唯一の選択肢 1つの方法は、 IXmlSerializable を実装し、オブジェクトxmlの書き込み/解析を自分で行うことです。
編集:コメントを読んだ後、 DataContractSerializer 面白そうです;)
「デフォルト」のXMLシリアル化を実行している場合は、パブリックプロパティのみが参照されます。 IXmlSerializable
を実装すると、シリアル化される内容を正確に制御できます。 「標準」の.NETシリアル化を実行している場合、プロパティではなくフィールドが参照されるため、追加のインターフェイスを実装しなくても、オブジェクトは正しくシリアル化されます。
何もせずに見つけたわけではありません。これは、生成されるXmlSerializer
がリフレクションを使用して新しいクラスを生成するためだと思います(これは新しいアセンブリにあるため、internal
メンバー/メソッドを表示できません)。
XmlSerialization PreCompilier を使用してコードを生成し、目的に合わせてコードを内部クラスに変更すると、多少のマイレージが発生する可能性があるため、次のようにします。
XmlSerializer serializer = new MyPersonXmlSerializer();
他のオプション(そしておそらく望ましい)は、自動生成されたコードが正しいことをするように導く IXmlSerializable を実装することです。
それは確かに可能です。ちなみに私がとても好きになったXElement
を使った解決策を説明したいと思います。必要がなければ、XmlSerializer
やDataContractSerializer
、または[DataContract]
や[Serializable]
などのクラスやプロパティのアノテーションを使用する必要はありません。そう。また、以下の例は、私の例でprivate set
をinternal set
と交換する方法を示しています。
using System;
using System.Linq;
using System.Xml.Linq;
namespace SerializationTesting
{
class Person
{
// Notice how this object type uses private setters, something that the traditional XmlSerializer will complain about if you don't use a wrapper class..
public string Name { get; private set; }
public DateTime Birthday { get; private set; }
public long HeightInMillimeters { get; private set; }
public Gender Gendrality { get; private set; }
// Generate a serialized XElement from this Person object.
public XElement ToXElement()
{
return new XElement("person",
new XAttribute("name", Name),
new XAttribute("birthday", Birthday),
new XAttribute("heightInMillimeters", HeightInMillimeters),
new XAttribute("gendrality", (long)Gendrality)
);
}
// Serialize this Person object to an XElement.
public static Person FromXElement(XElement x)
{
return new Person(
(string)x.Attribute("name"),
(DateTime)x.Attribute("birthday"),
(long)x.Attribute("heightInMillimeters"),
(Gender)(long)x.Attribute("gendrality")
);
}
public Person(string name, DateTime birthday, long heightInMillimeters, Gender gender)
{
Name = name;
Birthday = birthday;
HeightInMillimeters = heightInMillimeters;
Gendrality = gender;
}
// You must override this in conjunction with overriding GetHashCode (below) if you want .NET collections (HashSet, List, etc.) to properly compare Person objects.
public override bool Equals(object obj)
{
if (obj.GetType() == typeof(Person))
{
Person objAsPerson = (Person)obj;
return Name == objAsPerson.Name && Birthday == objAsPerson.Birthday && HeightInMillimeters == objAsPerson.HeightInMillimeters && Gendrality == objAsPerson.Gendrality;
}
return false;
}
// You must override this in conjunction with overriding Equals (above) if you want .NET collections (HashSet, List, etc.) to properly compare Person objects.
public override int GetHashCode()
{
return Name.GetHashCode() ^ Birthday.GetHashCode() ^ HeightInMillimeters.GetHashCode() ^ Gendrality.GetHashCode();
}
// This allows us to compare Person objects using the == operator.
public static bool operator ==(Person a, Person b)
{
return a.Equals(b);
}
// This allows us to compate Person objects using the != operator.
public static bool operator !=(Person a, Person b)
{
return !a.Equals(b);
}
}
public enum Gender
{
Male,
Female
}
class Program
{
static void Main(string[] args)
{
// Create first person (note how UTC time saves and loads properly when casting).
Person personOne = new Person("Alexandru", DateTime.UtcNow, 1000, Gender.Male);
// Save the first person to a local file on the hard disk.
personOne.ToXElement().Save("PersonOne.dat");
// Create second person (not using UTC time this time around).
Person personTwo = new Person("Alexandria", DateTime.Now, 900, Gender.Female);
// Save the second person to a local file on the hard disk.
personTwo.ToXElement().Save("PersonTwo.dat");
// Load the first person from a local file on the hard disk.
XDocument personOneDocument = XDocument.Load("PersonOne.dat");
Person personOneLoadedFromDocument = Person.FromXElement(personOneDocument.Elements().First());
// Load the second person from a local file on the hard disk.
XDocument personTwoDocument = XDocument.Load("PersonTwo.dat");
Person personTwoLoadedFromDocument = Person.FromXElement(personTwoDocument.Elements().First());
// Serialize the first person to a string and then load them from that string.
string personOneString = personOne.ToXElement().ToString();
XDocument personOneDocumentFromString = XDocument.Parse(personOneString);
Person personOneLoadedFromDocumentFromString = Person.FromXElement(personOneDocumentFromString.Elements().First());
// Check for equalities between persons (all outputs will be "true").
Console.WriteLine(personOne.Equals(personOneLoadedFromDocument));
Console.WriteLine(personTwo.Equals(personTwoLoadedFromDocument));
Console.WriteLine(personOne == personOneLoadedFromDocument);
Console.WriteLine(personTwo == personTwoLoadedFromDocument);
Console.WriteLine(personOne != personTwo);
Console.WriteLine(personOneLoadedFromDocument != personTwoLoadedFromDocument);
Console.WriteLine(personOne.Equals(personOneLoadedFromDocumentFromString));
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
上記のコンソールアプリケーションでのすべての同等性チェックの出力は、予想どおりtrue
になります。これは、エンコーディングやデータの解析方法を追跡する必要があるなどの煩わしさの影響を受けません。これは、すべてを実行するためです。また、クラスをXmlSerializer
のようにパブリックセッターに制限することもありません。