在LINQ中使用TryGetValue()?

时间:2010-07-19 11:23:34

标签: linq c#-4.0 tryparse out-parameters trygetvalue

此代码有效,但效率低,因为它会双重查找ignored字典。如何在LINQ语句中使用字典TryGetValue()方法使其更有效?

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语句中声明一个变量用于out参数。

3 个答案:

答案 0 :(得分:2)

您需要在查询之前声明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 :(得分:2)

(我的回答涉及使用TrySomething( TInput input, out TOutput value )方法的一般情况(例如IDictionary.TryGetValue( TKey, out TValue )Int32.TryParse( String, out Int32 ),因此它不会用OP自己的示例代码直接回答OP的问题。我是之所以在此发布答案,是因为该质量检查目前是截至2019年3月Google的“ linq trygetvalue”的最高结果。

使用扩展方法语法时,至少有这两种方法。

1。使用C#值元组,System.Tuple或匿名类型:

首先在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 lambda范围内,因此三元表达式变得不必要,就像这样:

// 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与匹配它的 any 方法一起使用,包括Int32.TryParseDouble.TryParseDictionary.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();

如果您仍要使用lambda,则替代定义使用值元组作为返回类型(需要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 :(得分:1)

使用外部变量,您不必担心它超出范围,因为LINQ表达式是一个闭包,可以使它保持活动状态。但是为了避免任何冲突,您可以将变量和表达式放在函数中:

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

...

var results = GetRecordQuery();

这样,只有查询才能访问s变量,而任何其他查询(从对GetRecordQuery的单独调用返回)都将拥有自己的变量实例。