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);
}
}
确实会涉及相当数量的代码,但它会相当简单,而且也容易测试。
非常好。我喜欢使用基类和派生类的构造函数的方式,以避免重复读取元素的代码。谢谢。 – 2012-08-04 21:21:56
其他用户注意事项:处理这个问题的最简单方法是将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
@SimonTewsi:就我个人而言,我不是那种自动化序列化的狂热粉丝。我经常发现我最终想要对整个过程进行更多的控制。但YMMV当然:) – 2012-08-04 23:15:04