1

コレクション内のオブジェクトがすべて同じ基本クラスから派生しているが、一部は 1 つの派生型であり、一部は別の派生型である可能性がある異種コレクションを照会する簡単な方法はありますか?

たとえば、クラス階層は次のとおりです。

public class Ship
{
    public string Name { get; set; }
    public string Description { get; set; }
}

public class SailingVessel : Ship
{
    public string Rig { get; set; }
    public int NumberOfMasts { get; set; }
}

public class MotorVessel : Ship
{
    public string Propulsion { get; set; }
    public decimal TopSpeed { get; set; }
}

そして、これがクエリしたい XML ドキュメントです。

<?xml version="1.0" ?>
<ships xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ship xsi:type="sailingVessel">
    <name>Cutty Sark</name>
    <description>Tea clipper</description>
    <rig>Ship</rig>
    <numberOfMasts>3</numberOfMasts>
  </ship>
  <ship xsi:type="sailingVessel">
    <name>Peking</name>
    <description>Windjammer of the Flying P Line</description>
    <rig>Barque</rig>
    <numberOfMasts>4</numberOfMasts>
  </ship>
  <ship xsi:type="motorVessel">
    <name>HMS Hood</name>
    <description>Last British battlecruiser</description>
    <propulsion>SteamTurbine</propulsion>
    <topSpeed>28</topSpeed>
  </ship>
  <ship xsi:type="motorVessel">
    <name>RMS Queen Mary 2</name>
    <description>Last transatlantic passenger liner</description>
    <propulsion>IntegratedElectricPropulsion</propulsion>
    <topSpeed>30</topSpeed>
  </ship>
  <ship xsi:type="motorVessel">
    <name>USS Enterprise</name>
    <description>First nuclear-powered aircraft carrier</description>
    <propulsion>Nuclear</propulsion>
    <topSpeed>33.6</topSpeed>
  </ship>
</ships>

XML ドキュメントをクエリして、その内容を Ship オブジェクトのリストに読み込むことができます。

        XDocument xmlDocument = XDocument.Load("Ships.xml")
        XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";

        var records = (from record in xmlDocument.Descendants("ship")
                       let type = record.Attribute(xsi + "type").Value                         
                       select new Ship
                       {
                           Name = (string)record.Element("name"),
                           Description = (string)record.Element("description")
                       }).ToArray<Ship>();

これは以下を返します。

Ship[0] (type: Ship):
    Name: Cutty Sark
    Description: Tea clipper
Ship[1] (type: Ship):
    Name: Peking
    Description: Windjammer of the Flying P Line
Ship[2] (type: Ship):
    Name: HMS Hood
    Description: Last British battlecruiser
Ship[3] (type: Ship):
    Name: RMS Queen Mary 2
    Description: Last transatlantic passenger liner
Ship[4] (type: Ship):
    Name: USS Enterprise
    Description: First nuclear-powered aircraft carrier

しかし、私が本当に生産できるようにしたいのはこれです:

Ship[0] (type: SailingVessel):
    Name: Cutty Sark
    Description: Tea clipper
    Rig: Ship
    NumberOfMasts: 3
Ship[1] (type: SailingVessel):
    Name: Peking
    Description: Windjammer of the Flying P Line
    Rig: Barque
    NumberOfMasts: 4
Ship[2] (type: MotorVessel):
    Name: HMS Hood
    Description: Last British battlecruiser
    Propulsion: SteamTurbine
    TopSpeed: 28
Ship[3] (type: MotorVessel):
    Name: RMS Queen Mary 2
    Description: Last transatlantic passenger liner
    Propulsion: IntegratedElectricPropulsion
    TopSpeed: 30
Ship[4] (type: MotorVessel):
    Name: USS Enterprise
    Description: First nuclear-powered aircraft carrier
    Propulsion: Nuclear
    TopSpeed: 33.6

基本の Ship オブジェクトではなく、SailingVessel オブジェクトまたは MotorVessel オブジェクトを適切に初期化するように LINQ クエリを変更するにはどうすればよいですか?

2 つの選択を実行し、それぞれの基本クラス プロパティ (名前と説明) のオブジェクト初期化を複製する必要がありますか? 考えられるのはそれだけですが、関連するコードの重複は嫌いです。または、基本クラスのプロパティを初期化し、必要に応じて SailingVessel (Rig、NumberOfMasts) または MotorVessel (推進力、TopSpeed) の追加プロパティを初期化する方法はありますか?

4

1 に答える 1

1

個人的には、各型に静的FromXElementメソッド (またはコンストラクター) を与えてから、次のDictionary<string, Func<Ship>>ように作成します。

var factories = new Dictionary<string, Func<Ship>>
{
     { "sailingVessel", SailingVessel.FromXElement },
     { "motorVessl", MotorVessel.FromXElement },
     ...
};

次に、クエリは次のようになります。

var records = from record in xmlDocument.Descendants("ship")
              let type = record.Attribute(xsi + "type").Value 
              select factories[type](record);

次のようなものを残して、共通のプロパティを抽出Shipする保護されたコンストラクターをクラスに与えることができます。XElement

public class SailingVessel : Ship
{
    public Rig Rig { get; set; }
    public int NumberOfMasts { get; set; }

    private SailingVessel(XElement element) : base(element)
    {
        Rig = (Rig) Enum.Parse(typeof(Rig), (string) element.Element("Rig"));
        NumberOfMasts = (int) element.Element("NumberOfMasts");
    }

    // Don't really need this of course - could put constructor calls
    // into your factory instead. I like the flexibility of factory 
    // methods though, e.g. for caching etc.
    public static FromXElement(element)
    {
        return new SailingVessel(element);
    }
}

確かにかなりの量のコードが含まれますが、すべてかなり単純で、テストも簡単です。

于 2012-08-04T06:44:27.707 に答える