【问题标题】:How to use DateTime.TryParseExact within a LINQ statement如何在 LINQ 语句中使用 DateTime.TryParseExact
【发布时间】:2014-03-05 21:09:30
【问题描述】:

给定以下源数据:

Dim inputs = {New With {.Name="Bob", .Date="201405030500"},
              New With {.Name="Sally", .Date="201412302330"},
              New With {.Name="Invalid", .Date="201430300000"}}

我编写了以下 LINQ 查询(目的是在 Select 之前添加一个 Where success 子句作为过滤器,但我已将其省略并在结果中包含 success 以进行调试) :

Dim result1 = From i in inputs
              Let newDate = New DateTime
              Let success = DateTime.TryParseExact(i.Date, "yyyyMMddHHmm", Globalization.CultureInfo.InvariantCulture, Globalization.DateTimeStyles.AssumeLocal, newDate)
              Select New With {
                  .Name = i.Name, 
                  .Success = success,
                  .Date = newDate
              }

但是,我得到以下结果,newDate 没有被 TryParseExact 填充:

因此,我重构了查询以将TryParseExact 提取到匿名方法中以获取以下内容:

Dim parseDate = Function(d As String)
                    Dim parsedDate As DateTime
                    Return New Tuple(Of Boolean, Date)(DateTime.TryParseExact(d, "yyyyMMddHHmm", Globalization.CultureInfo.InvariantCulture, Globalization.DateTimeStyles.AssumeLocal, parsedDate), parsedDate)
                End Function

Dim result2 = From i in inputs
              Let parsedDate = parseDate(i.Date)
              Select New With {
                  .Name = i.Name,
                  .Success = parsedDate.Item1,
                  .Date = parsedDate.Item2
              }

...这正确地给了我:

但是,我想找到一种完全在 LINQ 语句中执行此操作的方法,而无需使用匿名方法来创建元组。当然,我有一些可行的方法,但我想将其作为学术兴趣点。

我怀疑这是可能的,但我的问题是:

为什么result1 查询没有正确设置newDate 的解析日期?

(我考虑过惰性求值,但我认为它不适用于这里。)

更新:

  • 感谢Hamlet 向我展示了您实际上可以同时声明和调用匿名方法!头脑 = 吹!
  • 感谢Jim 建议使用 Nullable 而不是 Tuple!很有道理。
  • 感谢 Ahmad 建议关闭,这对我来说肯定比匿名方法更干净。

由于下面的答案,我将 LINQ 重构如下(包括Where 子句过滤器):

Dim result3 = From i in inputs
              Let parsedDate = Function(d) 
                                   Dim dtParsed As DateTime
                                   Return If(DateTime.TryParseExact(d, "yyyyMMddHHmm", Globalization.CultureInfo.InvariantCulture, Globalization.DateTimeStyles.AssumeLocal, dtParsed), dtParsed, New DateTime?)
                               End Function(i.Date)
              Where parsedDate.HasValue
              Select New With {
                  .Name = i.Name,
                  .Date = parsedDate.Value
              }

这实现了我正在寻找的东西,并且我相信编译器会优化生成的代码,但我仍然想确切地了解为什么 result1 不起作用。也许这是 Eric Lippert 或 John Skeet 的问题。

【问题讨论】:

    标签: .net vb.net linq tryparse


    【解决方案1】:

    我建议您使用专门为在 LINQ 查询中使用 TryParse 方法而创建的 TryParsers 库。

    Dim query = From i In inputs
        Let d = TryParsers.TryParse.DateTimeExact(i.Date, "yyyyMMddHHmm", Globalization.CultureInfo.InvariantCulture, Globalization.DateTimeStyles.AssumeLocal)
        Select New With {
              .Name = i.Name,
              .Success = Not d Is Nothing,
              .Date = d.Value
        }
    

    您不能使用范围变量(let)作为 ref / out 参数,因为let 关键字创建了一个只读变量(实际上它编译为匿名对象的属性)。

    【讨论】:

    • 感谢您在上面的评论中回答我关于 Let 子句的问题,以及向我介绍 TryParsers 库。这看起来好多了。
    • 您可能需要考虑检查.Success = Not d.HasValue() 而不是Not d Is Nothing
    • @JimWooley 没有区别
    【解决方案2】:

    您可以通过在查询之外声明newDate 来取消它,而不是在Let 子句中声明newDate。然后您的查询将像您最初一样引用它。

    Dim newDate As New DateTime
    Dim query = From i in inputs
                Let success = DateTime.TryParseExact(i.Date, "yyyyMMddHHmm", Globalization.CultureInfo.InvariantCulture, Globalization.DateTimeStyles.AssumeLocal, newDate)
                Select New With {
                  .Name = i.Name, 
                  .Success = success,
                  .Date = newDate
              }
    

    不管怎样,在 C# 中,我们需要为 DateTime.TryParseExact 方法使用 out 关键字。与第一个示例类似,尝试将 newDatelet 子句一起使用会导致 C# 编译器生成错误:

    不能将范围变量 'newDate' 作为 out 或 ref 参数传递

    【讨论】:

    • C# 对这种代码完全没有问题。这是两个带有闭包和委托的实现。 pastebin.com/CNeL54AT
    • @HamletHakobyan 根据您的代码,我们同意。我对 C# 的评论是指尝试使用 let 子句在 LINQ 查询中使用 newDate,就像 OP 在他们的第一个 sn-p...let newDate = ... 中所做的那样。由于不清楚,我已经更新了我的评论。谢谢!
    • 这更接近我正在寻找的东西,尽管它引入了一个(无害的)闭包。我仍然想了解的是为什么 Let 子句不起作用。 (如果它可以工作,它会将newDate 变量的范围缩小到 LINQ 表达式本身。)
    • >我仍然想了解的是为什么 Let 子句不起作用。因为let 关键字创建了一个“只读”变量(实际上它编译为匿名对象的属性)。
    【解决方案3】:

    您可能需要考虑为 .Date 属性使用 Nullable Date,并将 .Success 保留为 DateTime?.HasValue 的评估。然后,您需要修改 parseDate lambda 以返回 nullable,如下所示:

    Dim inputs = {New With {.Name="Bob", .Date="201405030500"},
                  New With {.Name="Sally", .Date="201412302330"},
                  New With {.Name="Invalid", .Date="201430300000"}}
    
    Dim parseDate = Function(d As String)
                        Dim parsedDate As DateTime? = DateTime.Now
                        If DateTime.TryParseExact(d, "yyyyMMddHHmm", Globalization.CultureInfo.InvariantCulture, Globalization.DateTimeStyles.AssumeLocal, parsedDate) Then
                            Return parsedDate
                        Else
                            Return Nothing
                        End If
                    End Function
    
    Dim result2 = From i in inputs
                  Select New With {
                      .Name = i.Name,
                      .Date = parseDate(i.Date)
                  }
    

    我实际上会将 parseDate 从 lambda 移出到静态辅助方法,以便您可以在代码的其他地方更轻松地重用它。

    Public Shared Function parseDate(d as String) as DateTime?
       Dim parsedDate As DateTime? = DateTime.Now
       If DateTime.TryParseExact(d, "yyyyMMddHHmm", Globalization.CultureInfo.InvariantCulture, Globalization.DateTimeStyles.AssumeLocal, parsedDate) Then
           Return parsedDate
       Else
           Return Nothing
       End If
    End Function
    

    【讨论】:

    • 您不能将TryParseExactNullable(Of DateTime) 作为参数传递,因为它是out 参数(类似于ByRef),但是让函数返回一个可为空的值是个好主意。
    • 你可以在调用 TryParseExact 之前将其设置为默认值(因此= DateTime.Now
    • 如果我保留 parseDate 方法,我同意这比使用元组要干净得多。然后我只需将Where success 调整为Where parsedDate.HasValue 以过滤掉我无法解析日期(因此不感兴趣)的记录。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-06
    • 2014-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多