I want to mutate a IEnumerable collection of XElement (change attributes, children etc) outside of the iteration loop it a separate method. I've discovered this isn't possible with references or assignment because the way IEnumerable just returns/yields a value.
This method adds child nodes to an XElement:
protected static void setErrorOnItem(ref XElement item, DiagErrorCode errorCode, DiagStatus errorLevel) {
XElement errorNode = new XElement("Error");
errorNode.SetAttributeValue("errorCode", errorCode.ToString());
errorNode.SetAttributeValue("errorLevel", errorLevel.ToString());
item.Element("Errors").Add(errorNode);
}
This works fine unless it's in the context of a loop of an XElement collection, which is handled by an IEnumerable iterator.
It's fine to to set attributes on the node from within the loop. But how can I put this logic in it's own method if references and assignment cannot be done?
public IEnumerable<XElement> GetRevisedVariationOverTime(IEnumerable<XElement> items) {
foreach(XElement item in items) {
if(sampleSize < RequiredSampleSize) {
// Works fine...
item.Attribute("Status").Value = DiagStatus.WARNING.ToString();
// Can't assign
item = getItemReplacement(item);
// Can't reference
setErrorOnItem(ref item);
}开发者_StackOverflow中文版
}
...
return items;
}
It should be safe to modify the items in the collection, as long as you don't modify the collection itself (directly or indirectly). Your code is clearly intending to change the items in the collection (item = getItemReplacement(item);
) so of course you wouldn't be able to do that.
If you want to replace the current element with your new element, you can do something this:
public IEnumerable<XElement> GetRevisedVariationOverTime(IEnumerable<XElement> items)
{
foreach(XElement item in items.ToList()) // note the ToList() call here
{
if(sampleSize < RequiredSampleSize)
{
item.Attribute("Status").Value = DiagStatus.WARNING.ToString();
var newItem = getItemReplacement(item);
setErrorOnItem(ref newItem, ...);
// do the replacement
item.Parent.ReplaceWith(newItem);
}
}
}
If your method takes an IEnumerable<XElement>
and returns the same, you can modify the sequence as you iterate over it. For example:
public IEnumerable<XElement> GetRevisedVariationOverTime(IEnumerable<XElement> items)
{
foreach ( var item in items ) {
if ( sampleSize < RequiredSampleSize) {
yield return GetItemReplacement( item );
}
else {
// return item as-is
yield return item;
}
}
}
XElement GetItemReplacement( XElement item )
{
// modify item in-place and return it,
// or create a whole new XElement and return that instead...
}
First of all, your GetRevisedVariationOverTime
method doesn't return an IEnumerable<XElement>
like its signature says, and your usage of setErrorOnItem
doesn't match its declaration... so I'll have to guess at what you're trying to do. (I also can't see that there's any need for the item
parameter to be ref
.)
I suspect you really want something like this:
public IEnumerable<XElement> GetRevisedVariationOverTime(IEnumerable<XElement> items) {
foreach(XElement item in items) {
if(sampleSize < RequiredSampleSize) {
// Works fine...
item.Attribute("Status").Value = DiagStatus.WARNING.ToString();
// yield a replacement item
yield return getItemReplacement(item);
// you probably want to do this before you yield the item
setErrorOnItem(item);
}
}
}
GetRevisedVariationOverTime
is then used to modify a sequence like this:
foreach (var item in GetRevisedVariationOverTime(items))
{
// the items returned are now those yielded by `getItemReplacement`
}
精彩评论