简体   繁体   中英

How to create a polymorphic array with linq to xml?

Is it possible to use LINQ to XML to create an array of a polymorphic type?

I have 3 classes, Base, Der1 and Der2. Base is base class for Der1 and Der2. I have an xml file in which I have nodes corresponding to Der1 and Der2 objets. What I'd like to do is to parse the file and fill in a List with Der1 and Der2 objects.

xml would look like this:

<nodes>
    <node type = "Der1" attr1="val1" />
    <node type = "Der2" attr2="val2" />
</nodes>

What I tried to do but what does not work is:

List<Base> PMList = 
(from der1node from xmlDoc.Descendants("nodes") 
where der1node.type == ("Der1") 
select new Der1()
{
    Attr1 = der1node.Attribute("attr1").Value
}
).Union<Base>
(from der2node from xmlDoc.Descendants("baseNode") 
where der2node.type == ("Der2") 
select new Der2()
{
    Attr2 = der2node.Attribute("attr2").Value
}
).ToList<Base>();

Here what I tried to do is to construct Der1 objects with type=Der1 and Der2 objects with type=Der2 and add them together into a List using Union.

However this does not work. How can I get objects of different type using LINQ to XML and put them in one polymorphic collection?

One way would be to cast the new instances to their base class before projecting them:

List<Base> PMList = (from baseNode from xmlDoc.Descendants("nodes") 
                     where baseNode.type == ("Der1") 
                     select (Base) new Der1() {
                         Attr1 = baseNode.Attribute("attr1").Value
                     }
                    ).Union(
                     from baseNode from xmlDoc.Descendants("baseNode") 
                     where baseNode.type == ("Der2") 
                     select (Base) new Der2() {
                         Attr2 = baseNode.Attribute("attr2").Value
                     }
                    ).ToList();

Cast the object form select to Base type:

(from der1node from xmlDoc.Descendants("nodes") 
where der1node.type == ("Der1") 
select new Der1()
{
    Attr1 = der1node.Attribute("attr1").Value
} as Base
)

When you create your sequences, just make sure you cast the projection to the base type that way it becomes an IEnumerable<Base> instead of IEnumerable<Der1> .

List<Base> PMList = 
    (from der1node from xmlDoc.Descendants("nodes") 
     where der1node.type == ("Der1") 
     select new Der1()
     {
         Attr1 = der1node.Attribute("attr1").Value
     } as Base).Union(
    (from der2node from xmlDoc.Descendants("baseNode") 
     where der2node.type == ("Der2") 
     select new Der2()
     {
         Attr2 = der2node.Attribute("attr2").Value
     } as Base)).ToList();

Alternatively you can call the Cast<Base>() on the sequence beforehand.

List<Base> PMList = 
    (from der1node from xmlDoc.Descendants("nodes") 
     where der1node.type == ("Der1") 
     select new Der1()
     {
         Attr1 = der1node.Attribute("attr1").Value
     }).Cast<Base>().Union(
    (from der2node from xmlDoc.Descendants("baseNode") 
     where der2node.type == ("Der2") 
     select new Der2()
     {
         Attr2 = der2node.Attribute("attr2").Value
     }).Cast<Base>()).ToList();

On a side note, did you consider using Concat instead of union? Union will remove duplicates, which in your current state shouldn't be issue.
However, if at some point you override equality of Der1 and Der2 some items might suddenly stop appearing in the list. It's good to be aware of that.

Anyways, here's how I would do it:

var items = xmlDoc.Descendants("nodes")
    .Where(d1 => d1.type == ("Der1"))
    .Cast<Base>()
    .Concat(xmlDoc.Descendants("nodes")
        .Where(d2 => d2.type = ("Der2"))
        .Cast<Base>()
    ).ToList();

I know this comes like 7 years later. But in case the order of the elements is relevant to your use case. Here I provide an alternative without using Union that preserves the order. The following code assumes that "nodes" is the root element .

// Explicitly specify the return value to the Base type
var nodes = xmlDoc.Root.Elements().Select<XElement, Base>((e, index) => {
    string type = (e.Attribute("type") != null)?e.Attribute("type").Value:null;
    if(type != null) {
        if(type.Equals("Der1")) {
            // If you have something like an order property, you can assign it to index
            return new Der1() { Attr1 =  e.Attribute("attr1").Value };
        }
        else if(type.Equals("Der2")) {
            return new Der2() { Attr2 =  e.Attribute("attr2").Value };
        }
        else {
            return null;
        }
    } else {
        // You can also throw an Exception and handle it.
        return null;
    }
}).ToList();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM