这有什么神奇之处?

时间:2013-02-19 19:41:43

标签: c# xml linq

我最近遇到了臭名昭着的Jon Skeet关于使用LINQ to XML的帖子。这段特殊的代码引起了我的注意:

// Customers is a List<Customer>
XElement customersElement = new XElement("customers",
    customers.Select(c => new XElement("customer", //This line is "magic"
        new XAttribute("name", c.Name),
        new XAttribute("lastSeen", c.LastOrder)
        new XElement("address",
            new XAttribute("town", c.Town),
            new XAttribute("firstline", c.Address1),
            // etc
    ));

我决定在我的应用程序中自己测试它,我设置了一个foreach循环:

foreach (var kvp in m_jobs) {    //m_jobs is a Dictionary<string,Job>
    m_xmlDoc.Root.Element("SCHED_TABLE").Add(
        kvp.Value.GenerateXmlNode())
    );
}

我修改为:

m_xmlDoc.Root.Element("SCHED_TABLE").Add(
    m_jobs.Select(job => job.Value.GenerateXmlNode())
};

其中GenerateXmlNode()是为特定作业项生成适当XML标记的方法。我不确定会发生什么,但是看起来它和我的foreach循环完全一样。我不太明白的是为什么?!此外,这被视为&#34;滥用&#34;或者&#34;功能&#34; LINQ?

编辑为清晰起见:我知道.Select将返回一个IEnumerable,其中包含我要求的内容,但我没有明确列举它。我理解.Add是如何工作的,因为它接受了可变数量的参数,但是我再也没有明确地枚举这些参数。那么......它是如何运作的?

4 个答案:

答案 0 :(得分:7)

XElement.Add方法看起来像这样:

public void Add(object content)
{
    if (content is IEnumerable)
    {
        foreach (object child in (IEnumerable)content)
            Add(child);
    }
    else
    {
        //process individual element
    }
}

因此,虽然从Add的公共接口不清楚,但您可以将一系列项目或单个项目传递给它,它将确定它在运行时的位置并采取相应的行动。

答案 1 :(得分:5)

没有魔力; Add方法接受objectparams object[] - 在内部它只是检查每个输入的一系列常见场景,包括IEnumerable等。然后它只是展开序列,添加它发现的子元素/属性。 LINQ返回(在此方案中)来自IEnumerable的{​​{1}}序列,使其完全可用。

答案 2 :(得分:2)

SelectLINQ extension method,可以应用于实施IEnumerable<T>的任何类型。作为参数,它接受委托或lambda expression(这里是lambda表达式)。此lambda表达式定义了一个ad-hoc函数,该函数应用于集合的每个元素。这里的元素由c表示。 Select产生IEnumerable<U>,其中U是lambda表达式返回的项的类型。换句话说,Select通过lambda表达式(此处为Customers to XElements)将类型T的元素转换为类型U的元素。

由于XElement构造函数的第二个参数也接受枚举,因此这种“魔法”是可能的。

public XElement(
    XName name,
    Object content
)

content可以是IEnumerable<XElement>等。 System.Xml.Linq命名空间非常灵活。还有从stringXName的隐式转换,允许您将字符串作为第一个参数传递。

答案 3 :(得分:1)

这个问题实际上是由三个为什么组成的。在魔术最大的秘密最终揭晓之前,我们首先需要打破魔术师的代码,以便我们能够看清问题:

Func<Customer, XElement> selector=
    c => {
        var xe=new XElement("address",
            new XAttribute("town", c.Town),
            new XAttribute("firstline", c.Address1)
            // , etc
            );

        return
            new XElement("customer", // This line is a part of the "magic"
                new XAttribute("name", c.Name),
                new XAttribute("lastSeen", c.LastOrder),
                xe
                );
    };

XElement customersElement=new XElement("customers", customers.Select(selector)); // This line is another part of the "magic"

CustomerLastOrderAddress1Town的字段或属性中假设Name类。以下是分开的问题和答案:

  

Q1 :有了引起您注意的代码段,为什么IEnumerable中的元素成员可以在没有您明确枚举的情况下访问?

A1 :元素与 lambda表达式一起传递。也就是说,传递给Select的参数是委托。因此,您可以访问使用c传递的元素的成员。我们在委托中调用的构造函数是XElement(XName name, params object[] content)的重载,您可以传递XAttributeXElement对象。

  

Q2 :使用代码的两种不同语法的代码段,为什么Add的语句与预计可枚举的Select的工作方式类似于调用{{{}的构造函数1}}使用选定的可枚举,产生等效结果,因为代码使用XElement

A2 foreachIEnumerable作为对象传递,是IEnumerable<T>XElement方法的重载构造函数,它继承自{ {1}}。

  

Q3 :根据Q2,为什么Add在作为对象传递的情况下仍然可以枚举?

A3 :类的实例总是是其类的(n)实例,即使您使用更大的类型,泛型类型或接口传递它。虽然在这里我们没有遇到界面问题,但我建议你看一下[this answer]来了解它。在XContainer的代码中,IEnumerable的传递参数将使用以下代码处理:

XContainer.Add(object content)

但是,如果IEnumerable是一个数组,而不是IEnumerable enumerable=content as IEnumerable; if(enumerable!=null) { foreach(object element in enumerable) { this.Add(element); } } else { this.AddString(GetStringValue(content)); } ,则会使用以下代码进行处理(可能是为了获得性能):

content

您可能想知道为什么即使传递数组也会调用as IEnumerable,这是因为数组object[] objArray=content as object[]; if(objArray!=null) { foreach(object element in objArray) { this.Add(element); } } 的重载是:

XContainer.Add(object content)

现在,你知道它是如何完成的。