4

我最近看到了臭名昭著的 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 循环完全一样。我不太明白的是为什么?!另外,这被认为是 LINQ 的“滥用”还是“功能”?

为清楚起见进行编辑:我知道 .Select 将返回一个 IEnumerable ,其中包含我所要求的内容,但我没有明确列举它。我了解 .Add 的工作原理,因为它接受可变数量的参数,但同样,我没有明确枚举传递这些参数。那么......它仍然如何工作?

4

4 回答 4

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您可以将一系列项目或单个项目传递给它,它会在运行时确定它是哪个并采取相应的行动。

于 2013-02-19T19:51:56.200 回答
5

没有魔法;该Add方法接受 aobject或 a params object[]- 并且在内部它简单地检查每个输入的一系列常见场景,包括IEnumerable等。然后它简单地展开序列,添加它发现的子元素/属性。LINQ(在这种情况下)IEnumerable从 中返回一个序列Select,这使得它完全可用。

于 2013-02-19T19:48:38.873 回答
2

Select是一种LINQ 扩展方法,可以应用于任何实现IEnumerable<T>. 作为参数,它接受委托或lambda 表达式(此处为 lambda 表达式)。这个 lambda 表达式定义了一个 ad-hoc 函数,该函数应用于集合的每个元素。这些元素在c这里表示。Select产生一个IEnumerable<U>whereU是 lambda 表达式返回的项目的类型。换句话说,通过 lambda 表达式(此处为 Customer 到 XElements)将类型元素Select转换为类型T元素。U

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

public XElement(
    XName name,
    Object content
)

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

于 2013-02-19T20:01:02.540 回答
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"

该类Customer假定在 、 和 的字段LastOrder或属性Address1中。以下是分开的问题和答案:TownName

Q1 : 看了你眼前一亮的代码片段,为什么IEnumerable不用你明确列举就可以访问 an 中 element 的成员?

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

Q2Add :使用代码的两种不同语法的片段,为什么带有投影可枚举的语句Select像调用XElement带有选定可枚举的构造函数一样产生与代码使用相同的结果foreach

A2 : IEnumerableorIEnumerable<T>作为对象传递给 的重载构造函数XElementAdd继承自 的方法XContainer

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

A3:一个类的实例总是它的一个(n)个实例,即使你用更大的类型、泛型类型或接口传递它。虽然在这里我们没有遇到界面问题,但我建议看一下[ this answer ]以了解这一点。在 的代码中XContainer.Add(object content),传递的 an 参数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),这是因为Addfor 数组的重载是:

public void Add(params object[] content) {
    this.Add(content);
}

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

于 2013-02-20T00:50:06.927 回答