views:

171

answers:

4

I have a class and a set of IEnumerables that are using this class to give me a list in a list. (See this question's answer for details.)

Here is the code:

public IEnumerable<IEnumerable<WorkItemColumn>> EnumerateResultSet(WorkItemCollection queryResults)//DisplayFieldList displayFieldList)
{
    foreach (WorkItem workItem in queryResults)
    {
        yield return EnumerateColumns(queryResults.DisplayFields, workItem);   
    }
}

public IEnumerable<WorkItemColumn> EnumerateColumns(DisplayFieldList resultSet, WorkItem workItem)
{
    foreach (FieldDefinition column in resultSet)
        yield return new WorkItemColumn { Name = column.Name, Value = workItem[column.Name], WorkItemForColumn = workItem};
}

And the class:

public class WorkItemColumn
{
    public string Name { get; set; }
    public object Value { get; set; }
    public WorkItem WorkItemForColumn { get; set; }
}

I set this result to be the ItemsSource for my ListBox

Form.QueryResultListSource = EnumerateResultSet(queryResults);

The problem hits when I try to catch an event for this list box:

public void QueryResultsSelectionChanged(SelectionChangedEventArgs e)
{
                                                   +-----------------+
                                                   v                 | 
    foreach (WorkItemColumn workItemColumn in e.AddedItems)          |
    {                                                                |
        AddWorkItemToPad(workItemColumn.WorkItemForColumn);          |
    }                                                +---------------|
                                                     |               |
                                                     v               |    
    foreach (WorkItemColumn workItemColumn in e.RemovedItems)        |
    {                                                                |
        RemoveWorkItemFromPad(workItemColumn.WorkItemForColumn);     |
    }                                                                |
                                                                     |    
}                                                                    |
                                                                     |
These items are where the problem is --------------------------------+

When I examine e.AddedItems[0] while debugging and it says its type is EnumerateColumns.

When I try cast to that type Visual Studio says (understandably) that EnumerateColumns is a method but is used like a type.

So, how can I reference this by type so I can do a foreach loop and get at the stuff inside it?


This was my updated code based on the answer:

public void QueryResultsSelectionChanged(SelectionChangedEventArgs e)
{
    foreach (IEnumerable<WorkItemColumn> workItemColumns in e.AddedItems)
    {
        if (workItemColumns.Count() > 0)
            AddWorkItemToPad(workItemColumns.First().WorkItemForColumn);                
    }

    foreach (IEnumerable<WorkItemColumn> workItemColumns in e.RemovedItems)
    {
        if (workItemColumns.Count() > 0)
            RemoveWorkItemFromPad(workItemColumns.First().WorkItemForColumn);    
    }
}
+2  A: 

The problem is that your first method isn't yielding each result given by the first - it's yielding the enumerable itself. The type of that a compiler-generated type for the iterator - but importantly, it implements IEnumerable<WorkItemColumn>.

You can cast each item to IEnumerable<WorkItemColumn> if you want - but it's not really clear what you're trying to do in QueryResultsSelectionChanged. You may want to do:

foreach (IEnumerable<WorkItemColumn> workItemColumns in e.AddedItems)
{                                                   
    foreach(WorkItemColumn workItemColumn in workItemColumns)
    {
        AddWorkItemToPad(workItemColumn.WorkItemForColumn); 
    }
}

etc... or you may not. It's tricky to say without knowing what you're really trying to do. Anyway, the crux of it is that you're currently trying to treat a sequence of items as a single item. Don't do that :)

Jon Skeet
I agree that this isn't really the right approach to take (unless, for some reason, he needs to be able to handle different sets of results...differently...but if that's the case then another approach altogether is probably called for), but that's not *strictly* a problem. It just seems like he's having difficulty getting the debugger to bind to the type instead of the method when going on local name alone.
Adam Robinson
That works. The inner foreach repeats work that really only needs to be done once, but I may not be able to get around that. (WorkItemForColumn is the same for all the items in workItemColumns, it is a back reference to the object that is the source of the object). The odd thing is that EnumerateColumns "type" has the workItem in it (in the watch list), but when I try to reference directly in the watch list it fails (says it does not contain a reference to workItem). Anyway, this works better than what I had. Thank you very much for taking the time to answer :)
Vaccano
You could add a `break` statement at the end of the inner `foreach`.
SLaks
+1  A: 

e.AddedItems[0] is an instance of a hidden iterator type generated by the compiler, which you cannot use directly.
However, it implements IEnumerable<WorkItemColumn>, so you can use it through that interface.

In other words:

foreach (IEnumerable<WorkItemColumn> colSet in e.RemovedItems)        
    foreah(WorkItemColumn workItemColumn in colSet)
        RemoveWorkItemFromPad(workItemColumn.WorkItemForColumn);     
SLaks
To be fair, you don't really know *what* the type is, since the code for those two properties is not provided. In any case, I don't believe that a compiler-generated type is going to use the type name as its name (compiler-generated names tend to use conventions that, while legal in IL, are illegal in the native language) so I have a feeling the problem is just that it needs to be forced to bind to the type rather than the method when using the name.
Adam Robinson
A: 

In the interest of completeness, you really should include the relevant code for AddedItems and RemovedItems.

Nonetheless, cast the object with the type name in fully-qualified form (meaning namespace included). This will make it bind to the type rather than the method signature, since the method won't be available at the type level but the subtype will.

Adam Robinson
+1  A: 

Although using an iterator is probably not the best approach for this problem, you can probably avoid the problem you're experiencing by storing the enumeration in an array like so:

using System.Linq;
...
Form.QueryResultListSource = EnumerateResultSet(queryResults).ToArray();

And change your EnumerateResultSet to look like:

public IEnumerable<WorkItemColumn[]> EnumerateResultSet(WorkItemCollection queryResults)//DisplayFieldList displayFieldList) 
{ 
    foreach (WorkItem workItem in queryResults) 
    { 
        yield return EnumerateColumns(queryResults.DisplayFields, workItem).ToArray();    
    } 
} 

As others have noted, using the yield keyword you are effectively returning a function that knows how to iterate over the items you wish to enumerate. But setting a data source to this enumerable does not simply execute it once and store the results. You'll likely have lots of performance problems by using iterators because anytime the results need to be used, the iteration needs to be performed all over again.

Josh Einstein
The values (and quantity of values) change under the hood so I want the re-iteration. The idea is that the WorkItemColumns are different for each query that is run. Thanks for the answer and the tip about the array.
Vaccano
While that may be true, an iterator is still probably something you want to use to *feed* the data but you probably want to hold it in a collection anyway. You can use a collection that supports change notification or you can look at the "Continuous LINQ" project on CodePlex for auto-refreshing queries. Just using an iterator won't give the UI enough information about when and how to best track changes.
Josh Einstein