我最近遇到了臭名昭着的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是如何工作的,因为它接受了可变数量的参数,但是我再也没有明确地枚举这些参数。那么......它是如何运作的?
答案 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
方法接受object
或params object[]
- 在内部它只是检查每个输入的一系列常见场景,包括IEnumerable
等。然后它只是展开序列,添加它发现的子元素/属性。 LINQ返回(在此方案中)来自IEnumerable
的{{1}}序列,使其完全可用。
答案 2 :(得分:2)
Select
是LINQ 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
命名空间非常灵活。还有从string
到XName
的隐式转换,允许您将字符串作为第一个参数传递。
答案 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"
在Customer
,LastOrder
,Address1
和Town
的字段或属性中假设Name
类。以下是分开的问题和答案:
Q1 :有了引起您注意的代码段,为什么
IEnumerable
中的元素成员可以在没有您明确枚举的情况下访问?
A1 :元素与 lambda表达式一起传递。也就是说,传递给Select
的参数是委托。因此,您可以访问使用c
传递的元素的成员。我们在委托中调用的构造函数是XElement(XName name, params object[] content)
的重载,您可以传递XAttribute
或XElement
对象。
Q2 :使用代码的两种不同语法的代码段,为什么
Add
的语句与预计可枚举的Select
的工作方式类似于调用{{{}的构造函数1}}使用选定的可枚举,产生等效结果,因为代码使用XElement
?
A2 :foreach
或IEnumerable
作为对象传递,是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)
现在,你知道它是如何完成的。