7

我一直在寻找在调用方法(或方法链)时处理空对象的最佳选择。

检查 if 条件是我们常见的做法:

if ( customObject != null ) {
    customObject.callMe();
}

可以通过使用扩展方法进一步改进:

Program customObject = null;
if (customObject.NotNull()) {
    customObject.CallMe();
}

public static bool NotNull(this object o) {
    return o == null;
}

请注意:我通常忽略!来自我的编程实践。因此,明智的做法是说扩展方法对我来说很好。

但是,当涉及到方法链时,处理变得非常复杂。

customObject.CallMe().CallMe2() ex... 

您如何认为它可以在 C# 中处理,以便CallMe仅在customObject不为 null时CallMe2调用,并且仅在CallMe返回非 null 对象时调用。

当然我可以使用 If 条件或三元运算符。但是,我想知道 vNext, C#5.0 是否可以提供一些东西。

4

6 回答 6

15

在即将到来的 C# 6 (vNext) 中,有?.运算符(Null Conditional Operator),可以轻松地为每个嵌套属性链接空引用检查。

这方面的一个例子是:

int? first = customers?.[0].Orders?.Count();

这是 Visual Studio UserVoice 站点中请求的功能

添加 ?。运算符到 C#

您可以在 Roslyn 的 Codeplex 站点上查看 C# 6.0 的所有新语言功能的状态:

C# 6 语言功能状态

于 2014-09-01T00:10:20.973 回答
4

您目前可以编写以下类型的扩展方法:

public static class Extensions
{
    public static R IfNotNull<T, R>(this T @this, Func<T, R> @select) where T : class
    {
        return @this.IfNotNull(@select, () => default(R));
    }

    public static R IfNotNull<T, R>(this T @this, Func<T, R> @select, Func<R> @default) where T : class
    {
        return @this != null ? @select(@this) : @default();
    }

    public static void IfNotNull<T>(this T @this, Action<T> @action) where T : class
    {
        if (@this != null)
        {
            @action(@this);
        }
    }
}

然后像这样称呼他们:

customObject 
    .IfNotNull(y => y.CallMe())
    .IfNotNull(y => y.CallMe2());
于 2014-09-01T00:56:53.390 回答
2

C# 5 没有任何东西可以简化您的代码。你被困在其中之一:

  • 嵌套 if 条件
  • 复杂的三元表达式
  • Englobing try/catch 捕捉 NullReferenceException (如果你的链中的 null 不是真的异常,你真的不应该这样做,即它不应该发生,但你想要一个 keepsafe)。

然而,未来可能会更光明,作为空传播算子?。是 C# 6 的一个提议的特性,并且已经实现的是 Roslyn,所以如果它在 C# 6 中没有有效,那将是一个很大的惊喜。使用这个运算符,您将能够编写

customObject?.CallMe()?.CallMe2()?.[...] 

并且编译器将为您创建嵌套的 if(甚至确保每个方法只调用一次)。所以,多一点耐心……

于 2014-09-01T00:15:40.547 回答
0

C# 6 确实有?运算符,否则您可以查看 monad,尤其是Maybemonad,例如herehereothers。是否值得在没有考虑到它们的非功能语言中使用 monad 是一个不同的问题。

于 2014-09-01T00:27:34.180 回答
0

由于人们正在撕毁假设您不是白痴的答案,因此这里假设您是白痴。

您应该检查customObject创建时间,因为这就是您知道为什么会得到 null 的时间点。如果你当时没有检查,现在最好的办法是让你的代码抛出异常。如果您仍然可以在没有明确需要的对象的情况下做一些完全有意义的事情,您可以在异常的捕获中修复它,但可能没有。

所有忽略意外空值重要性的机制都只是传递了之前没有注意到的失败,包括习惯性地过度使用所提出的空值传播运算符,以及所有在使用前检查对象的习惯。他们正在寻求对灾难的有效反应,以尽量减少对灾难的关注。

如果你用一个对象的空值来表示重要信息,那可能是一个糟糕的设计选择,你应该在引入空值时将它转换为更有用的信息。

在使用对象之前而不是在生成对象之后测试对象的意外空值并不能真正帮助任何人。这是一种代码气味,表明您错位了责任,可能是以下方式之一:

  1. 你不愿意挑战那些在没有充分解释的情况下做出尴尬的设计决定的人
  2. 你不觉得你可以控制你已经合并的东西,但又懒得包装它
  3. 你不相信 null 应该存在,并且你试图忽略它们,所以你没有考虑过任何关于它们的政策。

您指出的政策,即在不分析的情况下吃掉 null 并继续执行,是一个糟糕的政策。如果您打算保留它,那是我的另一个答案。你应该做一些合理的事情。

在许多工作场所,在任何地方放置一个空的catch块是对未来工作的清晰记录。所以它正在做某事。但是离开它永远不会有一个适合好的代码的选项。这样的块将更合理地将异常转换为响应,从而创建未来的工作来修复错误的来源,即使代码块实现了以更本地方式解决问题的变通方法。

于 2014-09-01T16:02:33.953 回答
-6

这是异常处理的重点。如果您想响应异常情况,例如从函数返回意外的null 捕获NullReferenceException并从那里调用您的恢复代码。异常本身将停止进一步的调用,因此如果您只想完成这一切,请不要实施恢复代码。 我不认为这是一个合理的回应,这样的事情通常至少应该被记录下来。

try
{
    customObject.CallMe().CallMe2()
}
catch (NullReferenceException ex)
{
   /* save enough details to determine why you got an unexpected null */
}

不要使用异常处理来完成正常的编程任务,但不要过度避免它,通过检查每一个可能的意外事件,无论多么罕见。确定您的代码中可能出现的故障(您可以做任何事情),并在每个合理大小的块的末尾包含一个健康的catch列表。

于 2014-09-01T00:04:50.220 回答