1. 程式人生 > >C# CuttingEdge.Conditions 驗證幫助類庫 文檔翻譯

C# CuttingEdge.Conditions 驗證幫助類庫 文檔翻譯

容易 endwith 代碼 lam vat ide 種類型 示例 isnull

項目主頁: https://archive.codeplex.com/?p=conditions

作者博客關於項目的文檔(翻譯原文): https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=38#Designed_Behavior

前言

CuttingEdge.Conditions 可幫助開發人員在其.NET 3.5代碼庫中編寫前後條件驗證。

編寫這些驗證很容易,它提高了代碼的可讀性和可維護性。

警告:

此帖子基於CuttingEdge.Conditions 的 預覽版本發布。雖然庫的大多數概念和行為都是相同的,但最終版本有一些更改。

其中最明顯的是刪除了 Requires() 和 Ensures{} 方法的擴展方法行為。

請註意,不再支持以下語法:c.Requires().IsNotNull() ,建議的語法是 Condition.Requires(c).IsNotNull()。

請在閱讀本文時牢記這一點。

編寫前置條件驗證可以提高代碼質量。具有驗證的代碼更易於理解,並允許開發人員更快地發現錯誤,主要是在開發期間發現這些問題而不是在調試期間。

然而,編寫前置條件驗證一直是編程中不太好處理的地方。編寫它需要時間,我和許多開發人員(甚至是我尊重的人)都會跳過編寫它們。

跳過前置條件驗證將導致代碼更難以使用並且可能被濫用。它允許開發人員傳遞一些無效的方法參數,

這會導致意外的程序行為以及來自調用堆棧內部的那些可怕的 NullReferenceException 等異常。它會導致更多的錯誤,從而花費更多的時間進行調試。

CuttingEdge.Conditions 庫試圖降低編寫前置條件驗證的障礙並使代碼更具可讀性,從而產生更好的代碼,更少的錯誤和更短的開發周期。

要了解 CuttingEdge.Conditions 如何嘗試實現這一目標,讓我們首先看看我們可能每天編寫的一些代碼。

這是一個前置條件驗證的例子,舊的實現方式:

 1 void TheOldFashionWay(int id, IEnumerable<int> col, 
2 DayOfWeek day) 3 { 4 if (id < 1) 5 { 6 throw new ArgumentOutOfRangeException("id", 7 String.Format("id should be greater " + 8 "than 0. The actual value is {0}.", id)); 9 } 10 11 if (col == null) 12 { 13 throw new ArgumentNullException("col", 14 "collection should not be empty"); 15 } 16 17 if (col.Count() == 0) 18 { 19 throw new ArgumentException( 20 "collection should not be empty", "col"); 21 } 22 23 if (day >= DayOfWeek.Monday && 24 day <= DayOfWeek.Friday) 25 { 26 throw new InvalidEnumArgumentException( 27 String.Format("day should be between " + 28 "Monday and Friday. The actual value " + 29 "is {0}.", day)); 30 } 31 32 // Do method work 33 }

對於這樣一些簡單的驗證,這是一些非常多的代碼!

以下是 CuttingEdge.Conditions 的實現方式:

 1 //舊的調用方式
 2 void TheConditionsWay(int id, IEnumerable<int> col, 
 3     DayOfWeek day)
 4 {
 5     id.Requires("id").IsGreaterThan(0);
 6     col.Requires("col").IsNotEmpty();
 7     day.Requires("day").IsInRange(DayOfWeek.Monday, DayOfWeek.Friday);
 8 
 9     // Do method work
10 }
11 
12 //新的調用方式
13 private static void Main(string[] args)
14 {
15   var id = 0;
16 
17   Condition.Requires(id)
18     .IsGreaterThan(20)
19     .IsInRange(1, 999)    // ArgumentOutOfRangeException on failure
20        .IsNotEqualTo(1000,"編號不能為 1000");   // throws ArgumentException on failure
21 
22   var text = "hahahahhaha";
23 
24   Condition.Requires(text, "字符串")
25                 
26     .StartsWith("h") // throws ArgumentException on failure
27     .EndsWith("a") // throws ArgumentException on failure
28        .Evaluate(text.Contains("abc") || text.Contains("cba")); // arg ex
29  }

這是完全不同的,不是嗎?

它不僅代碼少得多; 它也非常易讀。請註意,兩種方法都有完全相同的合同; 兩種方法拋出完全相同的異常!

除了這些正常的前提條件檢查,CuttingEdge.Conditions還允許您進行後置條件檢查。

與先決條件不同,違反後置條件純粹是內部原因。

它可以被認為是一個錯誤。在這種情況下拋出ArgumentException會明顯混淆使用該代碼的開發人員。由於存在這種差異,CuttingEdge.Conditions 對違反後置條件的代碼總會拋出一個 PostconditionException 。

以下是後置條件檢查的示例:

1 void TheConditionsWay(int id, IEnumerable<int> col, 
2     DayOfWeek day)
3 {
4     id.Requires("id").IsGreaterThan(0);
5     col.Requires("col").IsNotEmpty();
6     day.Requires("day").IsInRange(DayOfWeek.Monday, DayOfWeek.Friday);
7 
8     // Do method work
9 }

後置條件示例顯示了兩個有趣的事情。

首先,使用Ensures擴展方法來啟動後置條件驗證。

其次,方法調用可以以流暢的方式鏈接,如 IsNotNull 和 IsOfType 方法所示。

API

CuttingEdge.Conditions API有許多驗證方法,

可以輕松滿足98%的驗證需求。目前有51種不同檢查的88種擴展方法。API可分為七組:

  • 入口點方法
  • 空檢查方法
  • 鍵入檢查方法
  • 比較檢查
  • 集合檢查
  • 字符串檢查
  • 自定義驗證

下面我將列出 CuttingEdge.Conditions 當前beta 1版本中的所有方法。

方法的數量可能會隨著時間的推移而增長,如果您認為缺少驗證,請在此處或在Codeplex上發表評論。我會考慮將它們添加到庫中。

另請註意,只需將擴展方法放在您自己的項目中,您就可以使用自己的方法擴展API。

Entry point methods 入口點方法

入口點方法用於啟動驗證。可以在每個類型的每個變量上調用這些擴展方法。目前有兩種方法:

Requires (2 重載)

Ensures (3 重載)

Requires 可以用來寫先決條件,它在驗證失敗時拋出 ArgumentException 或 它的一個子類。

Ensures 可以用來寫後置條件。它將拋出 PostconditionException。

空檢查方法

如果參數需要值或不需要值,則可以使用空檢查。可以對引用類型和 Nullable <T> 結構執行這些檢查。

有兩種方法:

IsNotNull(2 重載)

IsNull(2 重載)

鍵入檢查方法

有兩種方法可用於類型檢查:

IsOfType

IsNotOfType

比較檢查

比較檢查驗證參數是否等於某個其他值或在給定範圍內。

它們可以在Nullable <T>結構和所有實現 IComparable 的類型上執行。目前有12種方法:

IsEqualTo(3 重載)

IsNotEqualTo(3 重載)

IsGreaterThan(3 重載)

IsNotGreaterThan(3 重載)

IsGreaterOrEqual(3 重載)

IsNotGreaterOrEqual(3 重載)

IsInRange(3 重載)

IsNotInRange(3 重載)

IsLessThan(3 重載)

IsNotLessThan(3 重載)

IsLessOrEqual(3 重載)

IsNotLessOrEqual(3 重載)

集合檢查

集合檢查可用於(至少)實現 IEnumerable 的類型。目前有18種方法:

Contains(2 重載)

DoesNotContain(2 重載)

ContainsAll(2 重載)

DoesNotContainAll(2 重載)

ContainsAny(2 重載)

DoesNotContainAny(2 重載)

IsEmpty

IsNotEmpty

HasLength

DoesNotHaveLength

IsLongerThan

IsNotLongerThan

IsLongerOrEqual

IsNotLongerOrEqual

IsShorterThan

IsNotShorterThan

IsShorterOrEqual

IsNotShorterOrEqual

字符串檢查

有一組單獨的方法可以驗證字符串,目前有16種方法:

Contains

DoesNotContain

StartsWith(2 重載)

DoesNotStartWith(2 重載)

EndsWith(2 重載)

DoesNotEndWith(2 重載)

HasLength

DoesNotHaveLength

IsEmpty

IsNotEmpty

IsNullOrEmpty

IsNotNullOrEmpty

IsLongerThan

IsLongerOrEqual

IsShorterThan

IsShorterOrEqual

自定義驗證

對於使用上述方法無法完成的所有檢查,使用 Evaluate 方法重載是一種解決方案。

第一個重載檢查並返回一個布爾值,將在它等於false時拋出異常。

第二個重載運行一個返回布爾值的指定 Expression。這允許開發人員定義lambda表達式。這兩個重載允許字面表達您想要的任何前置或後置條件。有一種方法:

Evaluate(2次重載)

以下是使用Evaluate的兩個示例:

1 // Evaluate with boolean
2 s.Requires("s").Evaluate(
3     s.StartsWith("hello") || s.EndsWith("world"));
4 // Evaluate using a lambda expression
5 s.Requires("s").Evaluate((str) => 
6     str.StartsWith("hello") || str.EndsWith("world"));

這兩個例子看起來很多,但它們實際上是完全不同的。

第一個示例使用布爾參數,檢查速度非常快。但是,它在失敗時缺少一個好的異常消息。

與 lambda 表達式的重載恰恰相反。

它必須在每次調用時編譯給定的表達式,因此不應該在代碼的性能敏感部分中使用它。但是在使用時,能夠拋出一個具有描述性的異常消息:

‘(str.StartsWith("hello") || str.EndsWith("world"))‘ should hold for s. The actual value is ‘world hello‘. Parameter name: s。

CuttingEdge.Conditions API經過精心設計,

我希望它以最直觀的方式運行(您可以在這裏閱讀更多關於設計過程和一些背景知識)。

下面是我做出的一些設計決策以及開發人員必須編寫的代碼的後果。如果這種行為很奇怪並且需要改變,請告訴我。

空值被認為小於任何非空值。

這與在.NET框架中驗證對象的方式一致。以下代碼行顯示此行為:

1 int? i = null;
2 // Next check will pass, because null is
3 // smaller than Int32.MinValue.
4 i.Requires().IsLessThan(Int32.MinValue);

包含空引用的集合參數被視為包含零元素。

對於用戶來說,這種行為可能看起來很奇怪。當檢查空引用時,庫的用戶可能希望 API 拋出 ArgumentNullException 。

但是在某些情況下,拋出異常將不是預期的行為,因此會導致API不一致。除此之外,選擇空引用將會始終失敗,將限制該 API 的有用性。

在某些情況下,用戶可能會發現空引用是有效值。即,用戶可以如下定義前提條件:“ 集合應該具有少於五個元素並且可以是空引用”。

他無法使用集合方法的 API 來表達這個前提條件。他將被迫使用 Evaluate 機制,該機制顯然不太可讀,並且不會拋出描述性的異常消息。

以下示例顯示了設計的行為:

 1 IEnumerable c = null;
 2 // Both HasLength and IsEmpty checks will
 3 // pass, because c equals null.
 4 c.Requires().HasLength(0);
 5 c.Requires().IsEmpty();
 6 // IsShorterThan will pass, because c is
 7 // clearly shorter than 5 characters.
 8 c.Requires().IsShorterThan(5);
 9 // When c equals null, it doesn‘t contain
10 // any elements, so we‘d expect
11 // the next lines to pass.
12 c.Requires().DoesNotContain("value");
13 c.Requires().DoesNotContainAny(new string[] { "a", "b" });

當空引用不是有效值時,開發人員可以使用 IsNotNull()方法。即使空引用是無效狀態,也不需要這樣做。以下示例顯示了一些示例:

1 // Use IsNotNull() to check for null.
2 c.Requires().IsNotNull().IsShorterThan(5);
3 // IsNotEmpty() will throw an ArgumentNullException
4 // when c equals null and an ArgumentException
5 // when c is empty but not null.
6 c.Requires().IsNotEmpty();

當該值列表不包含任何元素時,被檢查的集合被視為包含所有指定的值。

當指定的值列表為空時,對ContainsAll方法的調用將始終成功。

以下示例顯示了此行為:

1 Collection<int> c =
2     new Collection<int> { 1, 2, 3, 4, 5 };
3 // All checks will pass.
4 c.Requires().ContainsAll(new int[] { 1, 2 });
5 c.Requires().ContainsAll(new int[] { 1 });
6 c.Requires().ContainsAll(new int[0] { });
7 c.Requires().ContainsAll(null);

當該集合不包含任何元素時,被檢查的集合被認為不包含任何指定的值。

當指定的值列表為空時,對ContainsAny的調用將始終失敗。

以下示例顯示了此行為:

1 Collection<int> c = new Collection<int> { 1, 2, 3, 4, 5 };
2 // Next two checks will pass.
3 c.Requires().ContainsAny(new int[] { 1, 9 });
4 c.Requires().ContainsAny(new int[] { 1 });
5 // Next two checks will fail, because the
6 // specified lists are empty.
7 c.Requires().ContainsAny(new int[0] { });
8 c.Requires().ContainsAny(null);

空字符串被認為具有0個字符的長度。

這裏的理由與前面集合的描述大致相同。

以下示例顯示了此行為:

1 string s = null;
2 // Next check passes.
3 s.Requires().HasLength(0);
4 s.Requires().IsShorterThan(5);
5 s.Requires().IsLongerThan(-1);
6 // You should use IsEmpty() or IsNotNull()
7 // if null is not a valid value.
8 s.Requires().IsEmpty();
9 s.Requires().IsNotNull().HasLength(0);

空字符串和空字符串不被視為相等,並且有多種方法需要檢查。

以下示例顯示了此行為:

1 string s = null;
2 // The following checks will fail.
3 s.Requires().IsEqualTo(String.Empty);
4 s.Requires().IsEmpty();
5 // The following checks will pass.
6 s.Requires().IsNullOrEmpty();
7 s.Requires().IsNull();

一個 null 字符串 僅僅會包含 null 字符串 。這種情況適合使用 StartsWith,EndsWith 和 Contains 等方法。

這就是.NET 的 String 方法的工作原理。以不同方式實現它不僅非常困難,而且用戶不會期望這種行為。

以下示例顯示了此行為:

1 string s = null;
2 // All checks below will fail.
3 s.Requires().Contains(String.Empty);
4 String.Empty.Requires().Contains(null);
5 String.Empty.Requires().EndsWith(null);

檢查空引用是否屬於某種類型將始終成功。

這對於反向操作也是有效的。

警告:

設計的行為已更改。與下面的文本描述的相反,在最終版本中,使用null參數調用IsOfType將始終失敗,並且使用null參數調用IsNotOfType現在將始終成功。

下面的代碼是違反直覺的,最終的 API 模仿了C# ‘is‘ 運算符的行為。

在這裏,我們看到了與之前看到的集合和字符串相同的問題。用戶可以確定空引用有效。

如果沒有,他將不得不在他的代碼中添加一個IsNotNull檢查。以下示例顯示了此行為:

1 object o = null;
2 // Both checks below will pass, because o equals null.
3 o.Requires().IsOfType(typeof(string));
4 o.Requires().IsNotOfType(typeof(object));
5 // Use IsNotNull() when null is not a valid value.
6 o.Requires().IsNotNull().IsOfType(typeof(string));
7 o.Requires().IsNotNull().IsNotOfType(typeof(object));

在具體翻之前覺得驗證似乎是一件很簡單的事情,但是這篇文檔最後這幾條體現出了這一方面的水深。

該文檔與現在的 API 差距還是有一些的。而且該類庫也無人維護了。原本我是打算用這個類庫,或者是參考一下實現做些改進。

現在我覺得,我還是看看其他的類庫吧。如果有時間的話就自己去實現一套。(估計沒有的啦~

C# CuttingEdge.Conditions 驗證幫助類庫 文檔翻譯