【问题标题】:Safely dereferencing FirstOrDefault call in Linq c#在 Linq c# 中安全地取消引用 FirstOrDefault 调用
【发布时间】:2012-06-28 01:23:06
【问题描述】:

为了在我的代码中简洁起见,我希望能够执行以下操作:拥有一个集合,找到与 lambda 表达式匹配的第一个元素;如果存在,则返回属性或函数的值。如果不存在,则返回 null。

更新示例 w。类

让我们收集一些东西

class Stuff
{
    public int Id { get; set; }
    public string Value { get; set; }
    public DateTime? ExecutionTime { get; set; }
}

我的目标是在调用它时很好地返回

var list = new Stuff[] { new Stuff() { Id = 1, Value = "label", ExecutionTime = DateTime.Now } };

// would return the value of ExecutionTime for the element in the list
var ExistingTime = list.FirstOrDefault(s => s.Value.Contains("ab")).ExecutionTime;

// would return null
var NotExistingTime = list.FirstOrDefault(s => s.Value.Contains("zzz")).ExecutionTime; 

是否可以使用一些 linq-syntax-fu 或者我必须在继续之前明确检查返回值?

原始示例 w。字符串

var stuff = {"I", "am", "many", "strings", "obviously"};

// would return "OBVIOUSLY"
var UpperValueOfAString = stuff.FirstOrDefault(s => s.contains("bvi")).ToUpper();

// would return null
var UpperValueOfAStringWannabe = stuff.FirstOrDefault(s => s.contains("unknown token")).ToUpper();

评论: 我不应该在我的原始示例中使用字符串,因为它通过将问题集中在 ToUpper 方法和字符串类上而略微歪曲了问题。请考虑更新后的示例

【问题讨论】:

  • 也许写一些扩展方法可以代替/在ToUpper()之前使用?这可以例如将null 转换为string.Empty
  • 嗯,这就是我的想法:我可以创建一个 Func(fodt => fodt != null ? fodt.property : null);函数并在其中包装 FirstOrDefault 调用,我想知道是否有更清洁的东西

标签: c# linq syntax


【解决方案1】:

为什么不直接做:

stuff.Where(s => s.contains("bvi"))
     .Select(s => s.ToUpper())
     .FirstOrDefault()

如果你有一个“非默认默认值”,你可以这样做:

stuff.Where(s => s.contains("bvi"))
     .Select(s => s.ToUpper())
     .DefaultIfEmpty("Something Else")
     .First()

【讨论】:

  • +1,这确实是只使用 linq 扩展的好方法。 where 是否没有问题,即每次调用时都会分析整个集合,即使在集合中很早就有一个项目与谓词匹配?
  • @samy 不,如果有第一个,最终的FirstOrDefault 将在第一个上停止迭代。上面的 linq 都不需要完整列表解析才能工作,它都是延迟的。
  • @Samy:不,我刚才问过a very similar question。见Eric Lippert's answer
  • 没错。在调用FirstOrDefault(或First)之前,不会进行任何迭代。然后,元素只在链中迭代,直到返回一个。从这个意义上说,它是有效的。您可以通过将副作用放入 WhereSelect 并看到它只会迭代直到第一个匹配。
  • 大吃一惊! :) @TimSchmelter:非常感谢您的启发性阅读。
【解决方案2】:

我喜欢这个作为扩展方法:

public static U SelectMaybe<T, U>(this T input, Func<T,U> func)
{
    if (input != null) return func(input);
    else return default(U);
}

及用法:

var UpperValueOfAString = stuff.FirstOrDefault(s => s.Contains("bvi")).SelectMaybe(x => x.ToUpper());
var UpperValueOfAStringWannabe = stuff.FirstOrDefault(s => s.Contains("unknown token")).SelectMaybe(x => x.ToUpper());

这将链式返回默认值(在这种情况下为null,但对于该类型来说是正确的),或者调用相关函数并返回它。

【讨论】:

  • 确实,但它增加了价值 - 我很高兴为改进内容的人 +1。
  • 是的,这非常好,非常适合我的大多数情况;我可能也会将默认值作为 lambda 传递,但它简单且有用。谢谢
  • 请注意,我已将名称更改为SelectMaybe,因为它实际上只是一个检查nullSelect。您可以添加提供要返回的默认值的重载,或提供要测试的谓词。
【解决方案3】:

更新:

根据问题澄清,您不需要任何额外的代码来实现您想要的。只需使用 where 子句和 select 投影子句:

var theString = stuff
    .Where(s => s.contains("unknown token"))
    .Select(s => s.ToUpper())
    .FirstOrDefault();


旧答案

按照 cmets (and made generic in another answer) 中的建议,将 ToUpper 调用包装在扩展方法中。扩展方法归结为围绕静态方法调用的语法糖,因此它们可以很好地处理空值。

static class StringExtensions
{
    public static string PossiblyToUpper(this string s)
    {
        if (s != null)
            return s.ToUpper();

        return null;
    }
}

你的电话会变成:

var upperValueOfAStringWannabe = stuff
    .FirstOrDefault(s => s.contains("unknown token"))
    .PossiblyToUpper();

现在只是讨论简单支持单行样式代码的扩展方法是否比多行更具表现力 - 归根结底,表达代码的意图比代码看起来更重要喜欢。

我个人认为处理空值的扩展方法乍一看令人困惑,因为它们看起来像常规方法。

【讨论】:

  • 我从没想过使用静态扩展方法,这是个好主意,我会牢记在心。然而,Possibly yamen 提议的功能要多得多。还是谢谢你
  • @samy 没问题,是的,我同意通用解决方案非常好。我已经刷了! :-)
  • 嗯,这取决于你的意思是“为你做那件事”。可以通过不同的方式获得相同的结果。 .Select(x => x.ToUpper()) 在过滤之前使用 Where 可以做到这一点,它只会将返回的结果大写,以防它们为空时它什么也不做。和 FirstOrDefault()。唯一不好的是,大写将大写所有过滤的项目而不是第一个。
  • @MarinoŠimić 我已删除该声明。
  • @MarinoŠimić 这就是我在 cmets 中向 Ani 询问的问题。它可能看起来不多,但由于集合可能非常大,我想避免这种开销。
猜你喜欢
  • 2022-11-18
  • 2015-05-21
  • 1970-01-01
  • 2021-12-04
  • 1970-01-01
  • 2015-12-20
  • 1970-01-01
  • 1970-01-01
  • 2012-03-19
相关资源
最近更新 更多