Использование TryGetValue () в LINQ? - PullRequest
4 голосов
/ 19 июля 2010

Этот код работает, но неэффективен, потому что он выполняет двойной поиск в словаре ignored.Как я могу использовать метод словаря TryGetValue() в операторе LINQ, чтобы сделать его более эффективным?

IDictionary<int, DateTime> records = ...

IDictionary<int, ISet<DateTime>> ignored = ...

var result = from r in records
             where !ignored.ContainsKey(r.Key) ||
             !ignored[r.Key].Contains(r.Value)
             select r;

Проблема в том, что я не уверен, как объявить переменную в операторе LINQ для использования длявыходной параметр.

Ответы [ 3 ]

3 голосов
/ 26 марта 2019

(Мой ответ касается общего случая использования TrySomething( TInput input, out TOutput value ) методов (таких как IDictionary.TryGetValue( TKey, out TValue ) и Int32.TryParse( String, out Int32 )), и поэтому он не дает прямого ответа на вопрос ОП с помощью собственного кода ОП. Я публикую этот ответ здесь, потому чтоэтот QA в настоящее время является лучшим результатом Google для "linq trygetvalue" по состоянию на март 2019 г.).

При использовании синтаксиса метода расширения существуют как минимум эти два подхода.

1. Использование значения C #-tuples, System.Tuple или anonymous-types:

Сначала вызовите метод TrySomething в вызове Select и сохраните результат в кортеже значений в C # 7.0 (или в анонимном типе вболее старые версии C #, обратите внимание, что кортежи-значения предпочтительнее из-за их меньших издержек):

Использование кортежей-значений C # 7.0 (рекомендуется):

// Task: Find and parse only the integers in this input:
IEnumerable<String> input = new[] { "a", "123", "b", "456", ... };

List<Int32> integersInInput = input
    .Select( text => Int32.TryParse( text, out Int32 value ) ? ( ok: true, value ) : ( ok: false, default(Int32) ) )
    .Where( t => t.ok )
    .Select( t => t.value )
    .ToList();

Это на самом деле можно упроститьпользуясь еще одним изящным приемом, в котором переменная value находится в области действия всей лямбды .Select, поэтому тернарное выражение становится ненужным, например:

// Task: Find and parse only the integers in this input:
IEnumerable<String> input = new[] { "a", "123", "b", "456", ... };

List<Int32> integersInInput = input
    .Select( text => ( ok: Int32.TryParse( text, out Int32 value ), value ) ) // much simpler!
    .Where( t => t.ok )
    .Select( t => t.value )
    .ToList();

Использование анонимных типов в C # 3.0:

// Task: Find and parse only the integers in this input:
IEnumerable<String> input = new[] { "a", "123", "b", "456", ... };

List<Int32> integersInInput = input
    .Select( text => Int32.TryParse( text, out Int32 value ) ? new { ok = true, value } : new { ok = false, default(Int32) } )
    .Where( t => t.ok )
    .Select( t => t.value )
    .ToList();

Использование .NET Framework 4.0 Tuple<T1,T2>:

// Task: Find and parse only the integers in this input:
IEnumerable<String> input = new[] { "a", "123", "b", "456", ... };

List<Int32> integersInInput = input
    .Select( text => Int32.TryParse( text, out Int32 value ) ? Tuple.Create( true, value ) : Tuple.Create( false, default(Int32) ) )
    .Where( t => t.Item1 )
    .Select( t => t.Item2 )
    .ToList();

2.Используйте метод расширения

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

Он работает, объявляя свой собственный тип delegate для методов, имеющих второй параметр out.Linq не поддерживает их по умолчанию, потому что System.Func не принимает out параметров.Однако из-за того, как делегаты работают в C #, вы можете использовать TryFunc с любым методом, который ему соответствует, включая Int32.TryParse, Double.TryParse, Dictionary.TryGetValue и т. Д. ...

Для поддержки других методов Try... с большим количеством аргументов просто определите новый тип делегата и предоставьте вызывающей стороне способ указать дополнительные значения.

public delegate Boolean TryFunc<T,TOut>( T input, out TOut value );

public static IEnumerable<TOut> SelectWhere<T,TOut>( this IEnumerable<T> source, TryFunc<T,TOut> tryFunc )
{
    foreach( T item in source )
    {
        if( tryFunc( item, out TOut value ) )
        {
            yield return value;
        }
    }
}

Использование:

// Task: Find and parse only the integers in this input:
IEnumerable<String> input = new[] { "a", "123", "b", "456", ... };

List<Int32> integersInInput = input
    .SelectWhere( Int32.TryParse ) // The parse method is passed by-name instead of in a lambda
    .ToList();

Если вы все еще хотите использовать лямбду, альтернативное определение использует кортеж значения в качестве типа возвращаемого значения (требуется C # 7.0 или более поздняя версия):

public static IEnumerable<TOut> SelectWhere<T,TOut>( this IEnumerable<T> source, Func<T,(Boolean,TOut)> func )
{
    foreach( T item in source )
    {
        (Boolean ok, TOut output) = func( item );

        if( ok ) yield return output;
    }
}

Использование:

// Task: Find and parse only the integers in this input:
IEnumerable<String> input = new[] { "a", "123", "b", "456", ... };

List<Int32> integersInInput = input
    .SelectWhere( text => ( Int32.TryParse( text, out Int32 value ), value ) )
    .ToList();

Это работает, потому что C # 7.0 позволяет переменным, объявленным в выражении out Type name, использоваться в других значениях кортежа.

2 голосов
/ 19 июля 2010

Вам необходимо объявить переменную out перед запросом:

ISet<DateTime> s = null;
var result = from r in records
             where !ignored.TryGetValue(r.Key, out s)
                || !s.Contains(r.Value)
             select r;

Будьте осторожны с побочными эффектами, если запрос не будет оценен до тех пор, пока ...

1 голос
/ 08 января 2014

Используя внешнюю переменную, вам не нужно беспокоиться о том, что она выходит из области видимости, потому что выражение LINQ - это замыкание , которое будет поддерживать ее работу.Однако, чтобы избежать любых конфликтов, вы можете поместить переменную и выражение в функцию:

public IEnumerable GetRecordQuery() {
    ISet<DateTime> s = null;
    return from r in records
           ... 
}

...

var results = GetRecordQuery();

Таким образом, только запрос имеет доступ к переменной s и любые другие запросы (возвращаемые из отдельных вызововна GetRecordQuery) каждый будет иметь свой собственный экземпляр переменной.

...