我有一个源自反序列化XML文档的大型C#(3.0)对象结构。我需要知道层次结构中的深度变量是否为空。我现在这样做的方法是在向下检查每个父对象为null,但这会导致if语句的长时间重复。
我正在努力避免使用昂贵的try-catch块。
有更聪明的方法吗?
修改 例如,在将XML应用程序表反序列化为对象层次结构后,可能会有一个工资值
applicationForm.employeeInfo.workingConditions.salary
但为了安全地找到我必须写一些类似
的东西if (applicationForm.employeeInfo != null)
if (applicationForm.employeeInfo.workingConditions != null)
if (applicationForm.employeeInfo.workingConditions.salary != null)
因为如果其中一个父对象为null,那么简单地使用后者if语句当然会失败。
所以我正在寻找更智能的方法来处理这种情况。
答案 0 :(得分:5)
首先,如果您在多个地方重复逻辑,请将其封装在方法中。
其次,你不需要很多if语句,只需要一个有很多OR条件的语句:
if(parent==null ||
parent.Child == null ||
parent.Child.GrandChild == null ...
第三,“避免使用昂贵的try / catch块”可能是一种过早的优化,具体取决于您的场景。你是否真的试过这个并对它进行了分析,它是否真的会造成很大的开销?
答案 1 :(得分:5)
您遇到了A.B.C.D
中每个步骤可能产生null
的经典情况。虽然这是一种常见的情况,但令人惊讶的是,没有解决它的常见模式,除了使用带有大量或(||
)的大型if语句。
如果每个步骤都可以返回不同的类,那么可以应用很少使用的模式:使用带有方法链接的通用扩展方法。
广义扩展方法不是一个固定的术语,但我在这里使用它来强调ext。方法适用于所有类型的对象,因此 generalized 。根据{{3}},这是糟糕的设计。但是在某些偏远的情况下,就像你的一样,你可以使用它,只要你知道你正在做什么以及为什么。
技巧很简单:定义一个通用的扩展方法,并为具有默认构造函数的所有类进行推广。如果测试失败(对象为空),则该方法返回相同类型的新对象。否则,它将返回未更改的对象本身。
为什么这个对您的方案来说是一个好方法?因为您不需要更改任何现有的类,因为它可以理解并促进可读代码,因为它保持类型安全(编译时错误而不是运行时错误),并且与其他方法相比,它更简洁。
// extension method:
public static class SomeExtentionMethods
{
public static T SelfOrDefault<T>(this T elem)
where T : class, new() /* must be class, must have ctor */
{
return elem ?? new T(); /* return self or new instance of T if null */
}
}
// your code now becomes very easily readable:
Obj someObj = getYourObjectFromDeserializing();
// this is it applied to your code:
var mySalary = applicationForm.SelfOrDefault().
employeeInfo.SelfOrDefault().
workingConditions.SelfOrDefault().
salary;
// now test with one if-statement:
if(mySalary.IsEmpty())
// something in the chain was empty
else
// all's well that ends well :)
这种方法的优点在于它适用于所有类型的类(假设它们具有ctor),包括集合和数组。如果任何步骤是索引步骤,它仍然有效(取决于集合,无效索引可以返回null,默认或引发异常):
var x =
someObj.SelfOrDefault()
.Collection.SelfOrDefault()
.Items[1].SelfOrDefault()
.Mother.SelfOrDefault()
.Father.SelfOrDefault();
更新:扩展了一点并添加了更详细的示例
更新:将 重命名为NotNull
SelfOrDefault
,这意味着布尔值,遵循LINQ的命名约定(FirstOrDefault
等)并隐含了什么它确实。
更新:重写并重新组织,使代码更适用,希望整体更容易理解:)
答案 2 :(得分:3)
您可以嵌套三元运算符。仍然很痛苦,但没有嵌套ifs那么糟糕。
string salary = (applicationForm.employeeInfo == null) ? null :
(applicationForm.employeeInfo.workingConditions == null) ? null :
applicationForm.employeeInfo.workingConditions.salary;
如果您只是想知道它是否为空:
bool hasSalary = (applicationForm.employeeInfo == null) ? false :
(applicationForm.employeeInfo.workingConditions == null) ? false :
(applicationForm.employeeInfo.workingConditions.salary != null);
答案 3 :(得分:2)
你不能迭代吗?
for (SomeObject obj = someInstance; obj != null; obj = obj.Parent) {
//do something
}
答案 4 :(得分:1)
使用反射。
创建一个辅助方法,该方法为您的属性名称提供父对象和分层点标记字符串。然后使用PropertyInfo并使用递归在每次检查null时一次关闭一个属性,如果是,则返回null,否则继续沿着层次结构。
答案 5 :(得分:1)
由于你没有提供太多细节,我不得不填写很多空白。这是一个psuo-codeish示例,说明如何通过递归实现此目的
public bool doesHierarchyContainANull(MyObject ParentObject)
{
if (ParentObject.getMemberToCheckForNull() == null)
return true;
else if (ParentObject.isLastInHierarchy())
return false;
return doesHierarchyContainANull(ParentObject.getNextInHierarchy());
}
答案 6 :(得分:1)
这是我的简单解决方案:
if (applicationForm?.employeeInfo?.workingConditions?.salary != null)
此处任何这些对象(applicationForm、employeeInfo、workingConditions、salary)都可以为 null,并且它会正确解析。
答案 7 :(得分:0)
CheckForNull(MyType element)
{
if(element.ChildElement != null)
{
CheckForNull(element.ChildElement);
}
else
{
Console.WriteLine("Null Found");
}
}
答案 8 :(得分:0)
您需要一个递归函数来遍历结构并检查每个节点及其子节点是否为null。我正在研究一个样本,但IE崩溃了(典型!!)。稍后会发布一个。
示例
你可以做一些简单的事情(假设你只想检查结构是否有效),如下所示:
public void ValidateStructure()
{
Node root = // get the root node
try
{
ValidateChildren(root);
Console.WriteLine("All nodes are valid");
}
catch (NullReferenceException)
{
Console.WriteLine("Structure contained a null node.");
}
}
public void ValidateChildren(Node parent)
{
// an NullReferenceException would be raised here since parent would be null
foreach (var child in parent.Children)
{
ValidateChildren(child);
}
}
答案 9 :(得分:0)
我的解决方案如下:
public static TResult SafeGet<TSource, TResult>(this TSource source, Func<TSource, TResult> getResult) {
if (source == null)
return default(TResult);
try {
return getResult(source);
}
catch {
return default(TResult);
}
}
用法:
Test myTestObject = null;
var myStringOrNull = myTestObject.SafeGet(x => x.test.test.test.mySring);
答案 10 :(得分:0)
我喜欢Pontus Bremdahl的答案,但为我的用途添加了更多细节。 代码:
/// <summary>
/// Get a member in an object hierarchy that might contain null references.
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source">Base object to get member from.</param>
/// <param name="getResult">Member path.</param>
/// <param name="defaultResult">Returned object if object hierarchy is null.</param>
/// <returns>Default of requested member type.</returns>
public TResult SafeGet<TSource, TResult>(TSource source, Func<TSource, TResult> getResult, TResult defaultResult)
{
// Use EqualityComparer because TSource could by a primitive type.
if (EqualityComparer<TSource>.Default.Equals(source, default(TSource)))
return defaultResult;
try
{
return getResult(source);
}
catch
{
return defaultResult;
}
}
/// <summary>
/// Get a member in an object hierarchy that might contain null references.
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source">Base object to get member from.</param>
/// <param name="getResult">Member path.</param>
/// <returns>Default of requested member type.</returns>
public TResult SafeGet<TSource, TResult>(TSource source, Func<TSource, TResult> getResult)
{
// Use EqualityComparer because TSource could by a primitive type.
if (EqualityComparer<TSource>.Default.Equals(source, default(TSource)))
return default(TResult);
try
{
return getResult(source);
}
catch
{
return default(TResult);
}
}
用法:
// Only authenticated users can run this code
if (!HttpContext.Current.SafeGet(s => s.User.Identity.IsAuthenticated))
return;
// Get count limit from app.config
var countLimit = int.Parse(ConfigurationManager.AppSettings.SafeGet(
s => s.Get("countLimit"),
"100" // Default 100 if no value is present
));
// Is int 6 a class? Always no, but just to show primitive type usage.
var is6AClass = 6.SafeGet(i => i.GetType().IsClass);
CSharp版本6现在内置了它。 https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6#null-conditional-operators
有时代码往往会在null检查中淹没一点。空条件运算符只允许您在接收者不为null时访问成员和元素,否则提供null结果:
int? length = customers?.Length; // null if customers is null
Customer first = customers?[0]; // null if customers is null
空条件运算符可以方便地与空合并运算符一起使用??:
int length = customers?.Length ?? 0; // 0 if customers is null
空条件运算符表现出短路行为,只有当原始接收器不为空时,才会执行紧随其后的成员访问链,元素访问和调用:
int? first = customers?[0].Orders.Count();
这个例子基本上等同于:
int? first = (customers != null) ? customers[0].Orders.Count() : null;
除了customers
仅评估一次。除非?
具有非空值,否则不会执行紧跟customers
之后的成员访问,元素访问和调用。
当然,无条件运算符本身可以被链接,以防需要在链中多次检查null:
int? first = customers?[0].Orders?.Count();
请注意,调用(带括号的参数列表)不能紧跟?
运算符 - 这会导致语法含糊不清。因此,只有当代表在那里工作时,才能直接调用代理。但是,您可以通过委托上的Invoke
方法执行此操作:
if (predicate?.Invoke(e) ?? false) { … }
我们希望这种模式的一个非常常见的用途是触发事件:
PropertyChanged?.Invoke(this, args);
这是一种在触发事件之前检查null的简单且线程安全的方法。它是线程安全的原因是该功能仅评估左侧一次,并将其保存在临时变量中。
答案 11 :(得分:0)
使用Null monad。只要您using
它就可以在同一个文件或不同的文件中。
public static class NullMonad {
public static TResult SelectMany<TIn, TOut, TResult>(this TIn @in, Func<TIn, TOut> remainder, Func<TIn, TOut, TResult> resultSelector)
where TIn : class
where TOut : class
where TResult : class {
var @out = @in != null ? remainder(@in) : null;
return @out != null ? resultSelector(@in, @out) : null;
}
}
然后你可以使用LINQ:
var salary = from form in applicationForm
from info in form.employeeInfo
from cond in info.workingConditions
select cond.salary
如果工资存在,则返回工资;如果任何先前的工作结果为空,则返回null,而不抛出异常。
更好吗?不,只是为你节省了一点点重复,但看起来很酷。它还避免了创建所有未使用的&#34; OrDefault&#34;接受答案中的对象。