LINQ查询异类集合中具有相同基类的不同类型

LINQ查询异类集合中具有相同基类的不同类型

问题描述:

是否有任何简单的方法来查询异类集合,其中集合中的对象都来自同一个基类,但有些可能是一个派生类型,有些可能是另一个?LINQ查询异类集合中具有相同基类的不同类型

例如,这里有一个类层次结构:

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文档并阅读其内容入名单发货物件:

 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 

如何修改LINQ查询并初始化一个SailingVessel对象或MotorVessel对象合适,而不是基础Ship对象?

我是否必须做两个选择,并复制每个基类属性(名称和描述)的对象初始化?我能想到的就是这些,但我讨厌涉及代码的重复。或者,是否有一些方法可以初始化基类的属性,并根据需要选择初始化SailingVessel(Rig,NumberOfMasts)或MotorVessel(Propulsion,TopSpeed)的其他属性?

我个人给每个类型的静态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); 
    } 
} 

确实会涉及相当数量的代码,但它会相当简单,而且也容易测试。

+0

非常好。我喜欢使用基类和派生类的构造函数的方式,以避免重复读取元素的代码。谢谢。 – 2012-08-04 21:21:56

+0

其他用户注意事项:处理这个问题的最简单方法是将XML反序列化为对象:1)使用XSD.exe命令行工具从XML创建XSD; 2)使用XSD.exe从XSD创建实体类; 3)使用System.Xml.Serialization.XmlSerializer.Deserialize()将XML反序列化为实体。我的问题是实体类没有XML序列化属性的权力。也许使用TypeMappings进行反序列化会做到这一点,但在我看来,LINQ更直接。 – 2012-08-04 22:56:13

+0

@SimonTewsi:就我个人而言,我不是那种自动化序列化的*粉丝。我经常发现我最终想要对整个过程进行更多的控制。但YMMV当然:) – 2012-08-04 23:15:04