Может ли метод расширения LINQ создать новый KeyValuePair с new () .Value, когда предложение Where не выполнено - PullRequest
2 голосов
/ 11 марта 2011

У меня есть коллекция

List<KeyValuePair<string, Details>> x

, где

public class Details
{ 
    private int x;
    private int y;

    public Details()
    {
        x = 0;
        y = 0;
    }
    ...
}

Я использую LINQ для своей коллекции, чтобы вернуть экземпляр Details, где

x.Where(x => x.Key == aString).SingleOrNew().Value

Где .SingleOrNew определяется как

public static T SingleOrNew<T>(this IEnumerable<T> query) where T : new()
{            
    try
    {
       return query.Single();
    }
    catch (InvalidOperationException)
    {
       return new T();
    }
}

Так, если KeyValuePair<string, Details> не найден в списке x, который удовлетворяет условию Where, возвращается new KeyValuePair<string, Details>.

Но проблема в том, что new KeyValuePair<string, Details> включает значение NULL Подробнее .

Когда в предложении .Where не найдено совпадений, мне было интересно, можно ли использовать какой-либо метод LINQ (расширение), который бы возвращал new KeyValuePair<string, Details>, как SingleOrNew, но где .Value / Details часть KeyValuePair была инициализирована с использованием стандартного Details конструктора без параметров?Так что это не нуль!

Ответы [ 2 ]

5 голосов
/ 11 марта 2011

Используйте этот метод расширения вместо:

    public static T SingleOrNew<T>(this IEnumerable<T> query, Func<T> createNew) 
    {
        try
        {
            return query.Single();
        }
        catch (InvalidOperationException)
        {
            return createNew();
        }
    }

Возможно, вы также захотите иметь это, поэтому вам не нужно выражение Where ():

    public static T SingleOrNew<T>(this IEnumerable<T> query, Func<T,bool> predicate, Func<T> createNew) 
    {
        try
        {
            return query.Single(predicate);
        }
        catch (InvalidOperationException)
        {
            return createNew();
        }
    }

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

1 голос
/ 12 марта 2011

На самом деле я бы сделал это так

public static T SingleOrNew<T>(this IEnumerable<T> query, Func<T> createNew)
{
    using (var enumerator = query.GetEnumerator())
    {
        if (enumerator.MoveNext() && !enumerator.MoveNext())
        {
            return enumerator.Current;
        }
        else
        {
            return createNew();
        }
    }
}

Мне не нравится тестировать сценарий, ловя исключение.

Пояснение:

Прежде всего, я получаю перечислитель для перечисляемого. Перечислитель - это объект, имеющий метод MoveNext и свойство Current. Метод MoveNext устанавливает Current на следующее значение в перечислении (если оно есть) и возвращает значение true, если было другое значение для перемещения, и значение false, если его не было. Кроме того, перечислитель обернут внутри использования статистики, чтобы убедиться, что он утилизируется, как только мы закончим с ним.

Итак, после того, как я получу счетчик, я звоню MoveNext. Если это возвращает false, тогда перечислимое значение было пустым, и мы сразу перейдем к оператору else (минуя второй метод MoveNext из-за оценки короткого замыкания) и вернем результат createNew. Если тот первый вызов возвращает true, тогда нам нужно снова вызвать MoveNext, чтобы убедиться, что второго значения нет (потому что мы хотим, чтобы было значение Single). Если второй вызов вернет true, он снова перейдет к оператору else и вернет результат createNew. Теперь, если Enumerable имеет только одно значение, тогда первый вызов MoveNext вернет true, а второй вернет false, и мы вернем значение Current, которое было установлено при первом вызове MoveNext.

Вы можете изменить это на FirstOrNew, удалив второй вызов MoveNext.

...