views:

106

answers:

2

I'm trying to use the Visitor Pattern and I have as follows:

public class EnumerableActions<T> : IEnumerableActions<T>
{
private IEnumerable<T> itemsToActOn;


public EnumerableActions ( IEnumerable<T> itemsToActOn )
{
  this.itemsToActOn = itemsToActOn;
}

public void VisitAllItemsUsing ( IVisitor<T> visitor )
{
  foreach (T t in itemsToActOn)
  {
    visitor.Visit ( t );// after this, the item is unaffected.
  }
}

The visitor :

internal class TagMatchVisitor : IVisitor<Tag>
{
private readonly IList<Tag> _existingTags;

public TagMatchVisitor ( IList<Tag> existingTags )
{
  _existingTags = existingTags;
}

#region Implementation of IVisitor<Tag>

public void Visit ( Tag newItem )
{
  Tag foundTag = _existingTags.FirstOrDefault(tg => tg.TagName.Equals(newItem.TagName, StringComparison.OrdinalIgnoreCase));

  if (foundTag != null)
    newItem = foundTag; // replace the existing item with this one. 
}

#endregion
}

And where I'm calling the visitor :

IList<Tag> tags = ..get the list;
tags.VisitAllItemsUsing(new TagMatchVisitor(existingTags));

So .. where am I losing the reference ? after newItem = foundTag - I expect that in the foreach in the visitor I would have the new value - obviously that's not happening.

Edit I think I found the answer - in a foreach the variable is readonly.

http://discuss.joelonsoftware.com/default.asp?dotnet.12.521767.19

+1  A: 

For that to work, firstly "newItem" would have to be "ref". Secondly, you'd need to do something with the updated value after the delegate invoke (an enumerator doesn't offer any ability to update contents). But most updates to collections will break enumerators anyway!

Your replacing "newItem" will not be visible to the client otherwise. However, changes to properties of the tag (assuming it is a reference type and mutable) will be.

For this to work, itemsToActOn would have to be IList<T> - then you could use:

for(int i = 0 ; i < itemsToActOn.Count ; i++)
{
    T value = itemsToActOn[i];
    visitor.Visit(ref t)
    itemsToActOn[i] = value;
}

Or if you could use T Visit(T)

for(int i = 0 ; i < itemsToActOn.Count ; i++)
{
    itemsToActOn[i] = visitor.Visit(itemsToActOn[i]);
}
Marc Gravell
A: 

Yes, you're right - but I'm using an IEnumerable in that place so can't really do a for on it.

However I guess it's more correct to return a new list instead of affecting the current one. So I'm using a ValueReturningVisitor - inspired(taken?:) ) from Jean-Paul S. Boodhoo.

sirrocco
In which case, consider the LINQ Enumerable.Select method (for IEnumerable<T>), or List<T>.ConvertAll - both do this type of projection.
Marc Gravell