鉴于以下源数据:
Dim inputs = {New With {.Name="Bob", .Date="201405030500"},
New With {.Name="Sally", .Date="201412302330"},
New With {.Name="Invalid", .Date="201430300000"}}
我编写了以下LINQ查询(目的是在Where success
之前添加Select
子句作为过滤器,但我已将其删除并包含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
?
(我考虑过懒惰的评价,但我不认为它适用于此。)
更新
由于下面的答案,我按如下方式重构了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来说是一个问题。
答案 0 :(得分: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#中我们需要使用out
关键字作为DateTime.TryParseExact
方法。尝试将newDate
与let
子句一起使用,类似于第一个示例,将导致C#编译器生成错误:
无法将范围变量'newDate'作为out或ref参数传递
答案 1 :(得分:2)
我建议您使用TryParsers库,这是专为在LINQ查询中使用TryParse
方法而创建的。
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
)作为参考/输出参数,因为let
关键字会创建一个只读变量(实际上它会编译为匿名对象的属性) )。
答案 2 :(得分:1)
您可能需要考虑使用Nullable Date作为.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