在 LINQ 中使用 TryGetValue() 吗?

发布于 2024-09-10 07:29:09 字数 434 浏览 11 评论 0原文

此代码可以工作,但效率低下,因为它会双重查找 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 参数的变量。

This code works, but is inefficient because it double-lookups the ignored dictionary. How can I use the dictionary TryGetValue() method in the LINQ statement to make it more efficient?

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;

The problem is I'm not sure how to declare a variable within the LINQ statement to use for the out parameter.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

情感失落者 2024-09-17 07:29:09

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

使用扩展方法语法时,至少有以下两种方法:

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

调用 TrySomething<。首先在 Select 调用中使用 /code> 方法,并将结果存储在 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:

// 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 将其减少为单个调用。它在运行时应该更快,尽管这并不重要。

它的工作原理是为具有第二个 out 参数的方法声明自己的 delegate 类型。 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;
        }
    }
}

用法:

// 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 类型名称< 中声明变量/code> 用于其他元组值的表达式。

(My answer concerns the general case of using TrySomething( TInput input, out TOutput value ) methods (like IDictionary.TryGetValue( TKey, out TValue ) and Int32.TryParse( String, out Int32 ) and so it does not directly answer the OP's question with the OP's own exmaple code. I'm posting this answer here because this QA is currently the top Google result for "linq trygetvalue" as of March 2019).

When using the extension method syntax there are at least these two approaches.

1. Using C# value-tuples, System.Tuple, or anonymous-types:

Call the TrySomething method first in a Select call, and store the outcome in a value-tuple in C# 7.0 (or anonymous-type in older versions of C#, note that value-tuples should be preferred due to their lower overhead):

Using C# 7.0 value-tuples (recommended):

// 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();

This can actually be simplified by taking advantage of another neat trick where the value variable is in-scope for the entire .Select lambda, so the ternary expression becomes unnecessary, like so:

// 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();

Using C# 3.0 anonymous types:

// 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();

Using .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. Use an extension method

I wrote my own extension method: SelectWhere which reduces this to a single call. It should be faster at runtime though it shouldn't matter.

It works by declaring its own delegate type for methods that have a second out parameter. Linq doesn't support these by default because System.Func does not accept out parameters. However due to how delegates work in C#, you can use TryFunc with any method that matches it, including Int32.TryParse, Double.TryParse, Dictionary.TryGetValue, and so on...

To support other Try... methods with more arguments, just define a new delegate type and provide a way for the caller to specify more values.

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

Usage:

// 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();

If you still want to use a lambda, an alternative definition uses a value-tuple as the return type (requires C# 7.0 or later):

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

Usage:

// 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();

This works because C# 7.0 allows variables declared in an out Type name expression to be used in other tuple values.

渡你暖光 2024-09-17 07:29:09

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

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

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

You need to declare the out variable before the query :

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

Be careful of side effects if the query isn't evaluated until later, though...

不再见 2024-09-17 07:29:09

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

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

...

var results = GetRecordQuery();

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

Using an external variable, you don't need to worry about it going out of scope because the LINQ expression is a closure that will keep it alive. However to avoid any conflicts, you could put the variable and expression in a function:

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

...

var results = GetRecordQuery();

That way, only the query has access to the s variable, and any other queries (returned from separate calls to GetRecordQuery) will each have their own instance of the variable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文