在 LINQ 中使用 TryGetValue()?

Joe*_*ley 4 linq tryparse out-parameters trygetvalue c#-4.0

这段代码有效,但效率低下,因为它重复查找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;
Run Code Online (Sandbox Code Playgroud)

问题是我不确定如何在 LINQ 语句中声明一个用于 out 参数的变量。

Dai*_*Dai 9

(我的回答涉及使用TrySomething( TInput input, out TOutput value )方法的一般情况(比如IDictionary.TryGetValue( TKey, out TValue )andInt32.TryParse( String, out Int32 )所以它没有直接用 OP 自己的示例代码回答 OP 的问题。我在这里发布这个答案是因为这个 QA 目前是“linq trygetvalue "截至 2019 年 3 月)。

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

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();
Run Code Online (Sandbox Code Playgroud)

这实际上可以通过利用另一个巧妙的技巧来简化,其中value变量在整个.Selectlambda的范围内,因此三元表达式变得不必要,如下所示:

// 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();
Run Code Online (Sandbox Code Playgroud)

使用 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();
Run Code Online (Sandbox Code Playgroud)

使用 .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();
Run Code Online (Sandbox Code Playgroud)

2.使用扩展方法

我编写了自己的扩展方法:将SelectWhere其简化为单个调用。尽管它应该无关紧要,但它在运行时应该更快。

它通过delegate为具有第二个out参数的方法声明自己的类型来工作。默认情况下,Linq 不支持这些,因为System.Func不接受out参数。但是,由于委托在 C# 中的工作方式,您可以TryFunc任何与其匹配的方法一起使用,包括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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

// 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();
Run Code Online (Sandbox Code Playgroud)

如果您仍想使用 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

// 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();
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为 C# 7.0 允许在out Type name表达式中声明的变量用于其他元组值。


Tho*_*que 4

您需要out在查询之前声明变量:

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

不过,如果稍后才评估查询,请小心副作用......