[英]How can I do null checks within a lambda expression while using AutoMapper?
[英]How can I manage the onslaught of null checks?
通常,在編程中我們會遇到null
檢查顯示特別大的情況。 我說的是:
if (doc != null)
{
if (doc.Element != null)
{
... and so on
}
else
throw new Exception("Element cannot be null");
} else {
throw new Exception("document cannot be null");
}
基本上,整個事情變成了一個難以理解的噩夢,所以我想知道:有沒有更簡單的方法來描述我上面要做的事情? (除了空檢查,我還會不時得到string.IsNullOrEmpty
東西。)
接受的答案:我接受了具有此鏈接的答案,因為所描述的方法具有創新性,正是我想要的。 謝謝肖恩!
看看這篇文章: 一種流暢的C#參數驗證方法
它由一個Paint.NET開發人員編寫。 他使用擴展方法來簡化和清理空檢查代碼。
將它們向前推到函數的開頭,並將它們從執行工作的代碼部分中取出。 像這樣:
if (doc == null)
throw new ArgumentNullException("doc");
if (doc.Element == null)
throw new ArgumentException("Element cannot be null");
AndSoOn();
如果你只是想拋出異常,為什么不讓語言運行時為你拋出它?
doc.Element.whatever("foo");
您仍將獲得具有完整回溯信息的NullPointerException(或C#中的任何內容)。
您可能對Spec#感興趣:
Spec#是API契約的形式語言,它使用非空類型 ,前置條件,后置條件,對象不變量和模型程序(將整個運行歷史考慮在內的行為契約)構造擴展C#。
你可以讓調用者負責確保參數不為null。 Spec#使用感嘆號表示:
public static void Clear(int[]! xs) // xs is now a non-null type
{
for (int i = 0; i < xs.Length; i++)
{
xs[i] = 0;
}
}
現在是Spec#編譯器,它將檢測可能的空引用:
int[] xs = null;
Clear(xs); // Error: null is not a valid argument
作為旁注,您可能還想確保您沒有違反得墨忒耳法則 。
單獨(靜態?)函數調用:
public static void CheckForNullObject( object Obj, string Message) {
if(Obj == null){
throw new Exception(Message);
}
}
雖然這不是最好的選擇,但它會更具可讀性。
也許類的構造函數應該只在屬性初始化為適當的值時才創建對象? 也就是說,如果一個實例在創建時具有最小數量的屬性,那么只能創建一個實例,而不是創建一個基本上可以執行相同操作的Validate(Doc doc)方法,即檢查對象的有效性。
您可能對Null Object Pattern感興趣。
它幫助我過去在很多實例中擺脫了空檢查。
示例(C ++)
class IThing
{
public:
virtual void DoThing() = 0;
};
class NullThing : public IThing
{
public:
virtual void DoThing() { /*no-op*/}
};
class RealThing : public IThing
{
public:
virtual void DoThing() { /*does something real*/}
};
int main()
{
NullThing theNullInstance; /* often a singleton or static*/
IThing* thingy = &theNullInstance; /*the null value*/
// Do stuff that may or may not set thingy to a RealThing
thingy->DoThing(); // If is NullThing, does nothing, otherwise does something
// Can still check for null
// If NullThing is a singleton
if (thingy == &theNullInstance)
{
printf("Uhoh, Null thingy!\n");
}
}
如果一個典型的NullReferenceException
會做,請不要打擾檢查,只是讓運行時拋出它。 如果您需要添加上下文錯誤信息,以進行日志記錄或調試(或其他),請繼續將驗證分解為不同的方法。 在這種情況下,我仍然鼓勵你拋出NullReferenceException
,並嵌入原始異常。
當我被迫手動挖掘深度XML文檔時,我不得不做類似的事情。
通常,我嘗試在類的接口邊界強制執行null-correctness。 然后,我可以在我的私有方法中忽略空檢查。
如果你因為嵌套的ifs而感覺它是不可讀的,我的建議是重寫如下:
if (doc == null)
{
throw new Exception("document cannot be null");
}
if (doc.Element == null)
{
throw new Exception("Element cannot be null");
}
doc.Element.someMethod()
該代碼示例幾乎不可讀...當變量可能為null時,您必須檢查空值。 但是,如果要減少此操作,請確保返回對象的方法永遠不會返回null並始終返回完全有效且構造的對象。 如果它返回null,它會拋出異常。 返回null或-1或其他一些奇怪的約定不應該替代錯誤處理。
有幾個選項(其中一些已被其他人提及),所以我只是添加到列表中。
對於某些類型,使用null對象是有意義的。 在這種情況下,您必須確保方法永遠不會返回一個簡單的null,但總是返回一個實例(可能是null對象)。
如果你想使用Paige建議的靜態方法,你甚至可以把它變成一個擴展方法。 你可以做類似的事情:
private static void ThrowIfNull(this object o, string message) { if (o != null) return; throw new ArgumentNullException(message); }
合同是減少與空相關的問題和超級檢查的關鍵因素。
在某些情況下,有些方法可能/應該返回null。 如果你調用這樣的方法,那么你只需要檢查 。 最好盡早檢查。
並且有些方法不允許返回null。 不要檢查他們的返回值。 每種方法都有責任確保它履行自己的合同,因此作為該方法的調用者,您不必關心 。
有工具和語言功能可以幫助您記錄和檢查空檢查和合同的正確性。 抱歉,我不能再解釋了,因為我不是C#程序員。
如果你想深入了解,我推薦這四個問題。 它們主要以Java為中心,但C#也幾乎都是如此,有時答案甚至可以自定義為.net和c#。
除非你能夠用它們做一些聰明的事情,否則不要捕捉異常。
在這種情況下,您的異常處理程序在默認值上添加的值很少 - 也就是說,讓異常傳播回調用鏈。
在您的應用程序/線程的頂級,應始終有異常處理程序來處理這些未捕獲的異常。
編輯:投票失敗,我感到被誤解,也許我太敏感了;-)。 原始海報拋出的例外情況沒有任何價值。 他們沒有幫助最終用戶,他們也沒有幫助開發人員。
應用程序中的頂級異常處理程序應該捕獲這樣的異常並記錄它。 日志應包括堆棧跟蹤。 這告訴開發人員錯誤的來源,並消除了許多真正沒有用處的代碼。
如果異常增加了一些價值,那么我同意拋出它是合理的。 但這不是這種情況。 更糟糕的是,一旦你聲明這是一個很好的原則,你會看到更多的代碼行檢查空引用,以至於代碼會被它們弄得亂七八糟。
解決那些主張允許運行時拋出NullReferenceException的人:
我開始討論一個主題 ,即主動拋出ArgumentNullException或讓運行時拋出NullReferenceException是一個好主意。 基本的共識是采用主動方法而不是NullReferenceException方法是個好主意。 我並不一定說他們是對的,而在這里提倡否則的人是錯的。 我只是說社區可能不同意你的看法。
我想指出的是,如果你做了很多這類檢查,那么你做錯事的機會很大。 要么你的方法做得太多,要么你傳遞了太多的“tramp”參數(除了被傳遞給另一個函數之外沒有其他目的的參數)。 也許您應該考慮是否可以將代碼更多地分解為單獨的方法,或者將某些參數封裝到對象中。
這是一個使用LINQ表達式的解決方案,檢查鏈中的所有成員訪問,如果一切有效,則返回實際值:
public static T CheckedGet<T>(Expression<Func<T>> expr) where T : class
{
CheckAccess(expr);
return expr.Compile().Invoke();
}
public static void CheckAccess(Expression expr)
{
switch (expr.NodeType)
{
case ExpressionType.Lambda:
CheckAccess((expr as LambdaExpression).Body);
break;
case ExpressionType.MemberAccess:
{
CheckAccess((expr as MemberExpression).Expression);
var value = Expression.Lambda(expr).Compile().DynamicInvoke();
if (value == null)
{
throw new NullReferenceException(expr.ToString());
}
}
break;
case ExpressionType.ArrayIndex:
{
var binaryExpr = expr as BinaryExpression;
CheckAccess(binaryExpr.Left);
var arrayLength = (int)Expression.Lambda(Expression.ArrayLength(binaryExpr.Left)).Compile().DynamicInvoke();
var arrayIndex = (int)Expression.Lambda(binaryExpr.Right).Compile().DynamicInvoke();
if (arrayIndex >= arrayLength)
{
throw new IndexOutOfRangeException(expr.ToString());
}
var value = Expression.Lambda(expr).Compile().DynamicInvoke();
if (value == null)
{
throw new NullReferenceException(expr.ToString());
}
}
break;
case ExpressionType.Constant:
return;
}
}
用法,例如:
var val = CheckedGet(() => classA.PropertyA.ArrayB[0].FieldC);
它將通過適當的異常檢查所有成員訪問null
和有效數組長度。
如果你恰好使用C#考慮可以為空 。
如果您要檢查傳遞給部分公共API的參數,那么您應該在方法的最開始使用“保護條款”,如Dan Monego所寫。 如果您使用IsNotNull等方法創建或重用一些輔助類(如Assert)(或許您只需要其中一個),那么代碼將很容易閱讀。
您的代碼將如下所示:
Assert.IsNotNull(doc, "document");
Assert.IsNotNull(doc.Element", "Element");
//... and so on
如果您要檢查傳遞給您的某個私有方法的參數,那么您不應該進行此類檢查,而應該添加一個合同(在方法文檔中或者如果您使用某些工具,則使用某些特殊聲明)並始終期望使用正確的參數調用此方法。
你總是可以寫:
if (doc == null && doc.Element == null
{
}
但是你在我上面的一個帖子中丟失了粒度(例如Exception中的單獨消息)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.