C # - Yield дает непригодный для использования тип

У меня есть класс и набор IEnumerables, которые используют этот класс, чтобы дать мне список в списке. (Подробности см. в ответе на этот вопрос.)

Вот код:

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};
}

И класс:

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

Я установил этот результат как ItemsSource для моего ListBox.

Form.QueryResultListSource = EnumerateResultSet(queryResults);

Проблема возникает, когда я пытаюсь поймать событие для этого списка:

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 --------------------------------+

Когда я проверяю e.AddedItems[0] во время отладки, он говорит, что его тип — EnumerateColumns.

Когда я пытаюсь привести к этому типу, Visual Studio говорит (по понятным причинам), что EnumerateColumns является методом, но используется как тип.

Итак, как я могу ссылаться на это по типу, чтобы я мог выполнить цикл foreach и получить содержимое внутри него?


Это был мой обновленный код, основанный на ответе:

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);    
    }
}

person Vaccano    schedule 28.12.2009    source источник
comment
возможно, вам не хватает () после RemovedItems и AddedItems? Это методы или свойства?   -  person John Knoeller    schedule 29.12.2009


Ответы (4)


Проблема в том, что ваш первый метод не дает каждый результат, данный первым, - он дает само перечисляемое. Тип этого сгенерированного компилятором типа для итератора, но, что важно, он реализует IEnumerable<WorkItemColumn>.

Вы можете преобразовать каждый элемент в IEnumerable<WorkItemColumn>, если хотите, но не совсем понятно, что вы пытаетесь сделать в QueryResultsSelectionChanged. Вы можете сделать следующее:

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

и т.д... или вы не можете. Трудно сказать, не зная, что вы на самом деле пытаетесь сделать. В любом случае, суть в том, что в настоящее время вы пытаетесь рассматривать последовательность элементов как один элемент. Не делай этого :)

person Jon Skeet    schedule 28.12.2009
comment
Я согласен, что это не совсем правильный подход (если только по какой-то причине ему не нужно иметь возможность обрабатывать разные наборы результатов... по-разному... но если это так, то, вероятно, называется совсем другой подход for), но это не строго проблема. Просто кажется, что ему трудно заставить отладчик привязываться к типу, а не к методу, когда он работает только с локальным именем. - person Adam Robinson; 29.12.2009
comment
Это работает. Внутренний foreach повторяет работу, которую на самом деле нужно сделать только один раз, но я не смогу это обойти. (WorkItemForColumn одинаков для всех элементов в workItemColumns, это обратная ссылка на объект, который является источником объекта). Странно то, что тип EnumerateColumns содержит в себе workItem (в списке наблюдения), но когда я пытаюсь сослаться непосредственно в списке наблюдения, происходит сбой (говорит, что он не содержит ссылку на workItem). Во всяком случае, это работает лучше, чем то, что у меня было. Большое спасибо, что нашли время ответить :) - person Vaccano; 29.12.2009
comment
Вы можете добавить оператор break в конце внутреннего foreach. - person SLaks; 29.12.2009

e.AddedItems[0] — это экземпляр скрытого типа итератора, сгенерированный компилятором, который нельзя использовать напрямую.
Однако он реализует IEnumerable<WorkItemColumn>, поэтому его можно использовать через этот интерфейс.

Другими словами:

foreach (IEnumerable<WorkItemColumn> colSet in e.RemovedItems)        
    foreah(WorkItemColumn workItemColumn in colSet)
        RemoveWorkItemFromPad(workItemColumn.WorkItemForColumn);     
person SLaks    schedule 28.12.2009
comment
Честно говоря, вы на самом деле не знаете, что такое тип, поскольку код для этих двух свойств не предоставляется. В любом случае, я не верю, что сгенерированный компилятором тип будет использовать имя типа в качестве своего имени (имена, сгенерированные компилятором, как правило, используют соглашения, которые, хотя и допустимы в IL, недопустимы в родном языке), поэтому я у меня есть ощущение, что проблема заключается только в том, что ее нужно принудительно привязать к типу, а не к методу при использовании имени. - person Adam Robinson; 29.12.2009

Хотя использование итератора, вероятно, не лучший подход к этой проблеме, вы, вероятно, можете избежать проблемы, с которой вы столкнулись, сохранив перечисление в массиве следующим образом:

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

И измените свой EnumerateResultSet, чтобы он выглядел так:

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

Как отмечали другие, используя ключевое слово yield, вы фактически возвращаете функцию, которая знает, как перебирать элементы, которые вы хотите перечислить. Но установка источника данных для этого перечислимого не просто выполняет его один раз и сохраняет результаты. У вас, вероятно, будет много проблем с производительностью при использовании итераторов, потому что каждый раз, когда необходимо использовать результаты, итерацию необходимо выполнять снова и снова.

person Josh    schedule 28.12.2009
comment
Значения (и количество значений) меняются под капотом, поэтому я хочу повторить итерацию. Идея состоит в том, что WorkItemColumns различны для каждого выполняемого запроса. Спасибо за ответ и подсказку про массив. - person Vaccano; 29.12.2009
comment
Хотя это может быть правдой, итератор, вероятно, по-прежнему является чем-то, что вы хотите использовать для подачи данных, но вы, вероятно, все равно захотите хранить их в коллекции. Вы можете использовать коллекцию, поддерживающую уведомления об изменениях, или просмотреть проект Continuous LINQ в CodePlex для автоматического обновления запросов. Простое использование итератора не даст пользовательскому интерфейсу достаточно информации о том, когда и как лучше всего отслеживать изменения. - person Josh; 29.12.2009

В интересах полноты вы действительно должны включить соответствующий код для AddedItems и RemovedItems.

Тем не менее, приведите объект с именем типа в полной форме (имея в виду пространство имен). Это заставит его привязываться к типу, а не к сигнатуре метода, поскольку метод не будет доступен на уровне типа, но будет доступен подтип.

person Adam Robinson    schedule 28.12.2009