2014-03-05 46 views
2

考慮下面的源數據中使用DateTime.TryParseExact:如何在LINQ聲明

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填充:

Results without a parsed Date

所以,我重構查詢到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 
       } 

...這正確地給了我:

Results with a parsed Date

但是,我想找到一種方法在LINQ語句中完全執行此操作,而不需要nee d使用匿名方法創建一個元組。當然,我已經有了一些可行的方法,但我希望將其作爲學術興趣點。

我懷疑這是可能的,但我的問題是:

爲什麼不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 
       } 

Results with a parsed Date

這實現了我要找的,和我相信編譯器會優化生成的代碼,但我仍然想明白爲什麼result1不起作用。也許這是Eric Lippert或John Skeet的問題。

回答

2

我建議你使用的是創建特別是使用TryParse方法在LINQ查詢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)作爲參考輸入/輸出參數,因爲let關鍵字創建讀取變量(實際上它編譯匿名對象的屬性)。

+0

感謝您回答我關於上面評論中的「Let'子句的問題,以及向TryParsers庫介紹我。這看起來好多了。 – MCattle

+1

你可能要考慮檢查'.Success = Not d.HasValue()'而不是'Not d Is Nothing' –

+0

@JimWooley沒有任何區別 – hazzik

2

而不是在Let子句中聲明newDate,您可以通過在查詢外部聲明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方法。嘗試使用newDatelet條款,類似於第一個例子,將導致C#編譯器產生一個錯誤:

Cannot pass the range variable 'newDate' as an out or ref parameter

+1

C#與這類代碼沒有任何問題。這裏有兩個閉包和委託的實現。 http://pastebin.com/CNeL54AT –

+0

@HamletHakobyan基於你的代碼,我們達成了一致。我對C#的評論指的是嘗試在LINQ查詢中使用'newDate',使用'let'子句,就像OP在其第一個片段中所做的那樣...'let newDate = ...'。我已經更新了我的評論,因爲它不清楚。謝謝! –

+1

儘管它引入了一個(無害的)閉包,但它更接近我所尋找的內容。我還想知道的是爲什麼Let子句不起作用。 (如果它可以工作,它會將'newDate'變量的範圍縮小到LINQ表達式本身的範圍內。) – MCattle

1

你可能要考慮使用可空日期爲.Date財產並離開.Success是對DateTime?.HasValue的評估。這樣,你會需要修改parseDate拉姆達返回可空,如下所示:

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這樣你就可以重複使用它在你的代碼的其他地方更容易。

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 
+0

您不能將'TryParseExact'作爲參數傳遞給''Nullable(Of DateTime)'',因爲它是'out'參數(與'ByRef'相當),但讓函數返回一個可爲空的值是個好主意。 –

+0

如果您在調用TryParseExact(因此'= DateTime.Now')之前將其設置爲默認值,那麼您可以將其設置爲默認值。 –

+0

如果我保留了parseDate方法,我確實認爲這比使用Tuple更清晰。然後,我只需將'Where success'調整到'Where parsedDate.HasValue'來篩選出我無法解析日期的記錄(因此我不感興趣)。 – MCattle