35

编辑 2015这个问题及其答案不再相关。在 C# 6 出现之前就有人问过它,它具有空传播操作符 (?.),它避免了在这个问题和后续答案中讨论的 hacky-workarounds。截至 2015 年,在 C# 中,您现在应该使用 Form.ActiveForm?.ActiveControl?.Name。


我一直在考虑 .NET 中的 null 传播问题,这通常会导致丑陋的重复代码,如下所示:

尝试 #1 常用代码:

string activeControlName = null;
var activeForm = Form.ActiveForm;
if (activeForm != null)
{
    var activeControl = activeForm.ActiveControl;
    if(activeControl != null)
    {
        activeControlname = activeControl.Name;
    }
}

StackOverflow 上有一些关于 Maybe<T> monad 的讨论,或者使用某种“if not null”扩展方法:

尝试#2,扩展方法:

// Usage:
var activeControlName = Form.ActiveForm
                          .IfNotNull(form => form.ActiveControl)
                          .IfNotNull(control => control.Name);

// Definition:
public static TReturn IfNotNull<TReturn, T>(T instance, Func<T, TReturn> getter)
    where T : class
{
    if (instance != null ) return getter(instance);
    return null;
}

我认为这更好,但是,重复的“IfNotNull”和 lambda 有点语法混乱。我现在正在考虑这个设计:

尝试 #3,Maybe<T> 与扩展方法

// Usage:
var activeControlName = (from window in Form.ActiveForm.Maybe()
                         from control in window.ActiveControl.Maybe()
                         select control.Name).FirstOrDefault();

// Definition:
public struct Maybe<T> : IEnumerable<T>
      where T : class
{
    private readonly T instance;

    public Maybe(T instance)
    {
        this.instance = instance;
    }

    public T Value
    {
        get { return instance; }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return Enumerable.Repeat(instance, instance == null ? 0 : 1).GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

public static class MaybeExtensions
{
    public static Maybe<T> Maybe<T>(this T instance)
        where T : class
    {
        return new Maybe<T>(instance);
    }
}

我的问题是:这是对扩展方法的恶意滥用吗?它比旧的通常的空检查更好吗?

4

8 回答 8

18

有趣的是,这么多人独立选择了这个名字IfNotNull,因为在 C# 中 - 它必须是最明智的名字!:)

我在 SO 上发现的最早的一个:使用这种(基于扩展方法的)速记的可能陷阱

我的一个(不了解上述内容):C#中的管道转发

另一个最近的例子:如何检查深度 lambda 表达式中的空值?

IfNotNull扩展方法可能不受欢迎有几个原因。

  1. 有些人坚持认为,如果扩展方法的this参数是null. 我不同意方法名称是否清楚。

  2. 应用范围太广的扩展往往会使自动完成菜单变得混乱。这可以通过正确使用命名空间来避免,这样它们就不会惹恼不想要它们的人。

IEnumerable也尝试过这种方法,只是作为一个实验,看看我可以扭曲多少东西来适应 Linq 关键字,但我认为最终结果的可读性不如IfNotNull链接或原始命令式代码。

我最终得到了一个Maybe带有一个静态方法(不是扩展方法)的简单的自包含类,这对我来说效果很好。但是,我和一个小团队一起工作,而我的下一位最资深的同事对函数式编程和 lambdas 等感兴趣,所以他并没有因此而退缩。

于 2009-07-28T19:13:30.097 回答
14

尽管我很喜欢扩展方法,但我认为这并没有什么帮助。你仍然有重复的表达(在单子版本中),这只是意味着你必须Maybe向每个人解释。在这种情况下,增加的学习曲线似乎没有足够的好处。

IfNotNull版本至少设法避免重复,但我认为它仍然有点过于冗长而实际上没有更清晰。

也许有一天我们会得到一个空安全的解引用运算符......


顺便说一句,我最喜欢的半邪恶扩展方法是:

public static void ThrowIfNull<T>(this T value, string name) where T : class
{
    if (value == null)
    {
        throw new ArgumentNullException(name);
    }
}

这可以让你把这个:

void Foo(string x, string y)
{
    if (x == null)
    {
        throw new ArgumentNullException(nameof(x));
    }
    if (y == null)
    {
        throw new ArgumentNullException(nameof(y));
    }
    ...
}

进入:

void Foo(string x, string y)
{
    x.ThrowIfNull(nameof(x));
    y.ThrowIfNull(nameof(y));
    ...
}

参数名称仍然有令人讨厌的重复,但至少它更整洁。当然,在 .NET 4.0 中我会使用代码合同,这就是我现在要写的内容……堆栈溢出是很好的工作避免;)

于 2009-07-28T18:53:48.093 回答
1

如果您想要一个扩展方法来减少嵌套的 if 就像您拥有的那样,您可以尝试这样的事情:

public static object GetProperty(this object o, Type t, string p)
{
    if (o != null)
    {
        PropertyInfo pi = t.GetProperty(p);
        if (pi != null)
        {
            return pi.GetValue(o, null);
        }
        return null;
    }
    return null;
}

因此,在您的代码中,您只需执行以下操作:

string activeControlName = (Form.ActiveForm as object)
    .GetProperty(typeof(Form),"ActiveControl")
    .GetProperty(typeof(Control),"Name");

由于反射的缓慢,我不知道我是否想经常使用它,而且我真的不认为这比替代方案好多少,但它应该可以工作,无论你是否在方式...

(注意:我可能把这些类型弄混了):)

于 2009-07-28T19:11:33.737 回答
1

如果您正在处理 C# 6.0/VS 2015 及更高版本,他们现在有一个用于空传播的内置解决方案:

string ans = nullableString?.Length.ToString(); // null if nullableString == null, otherwise the number of characters as a string.
于 2015-10-08T16:42:10.340 回答
0

初始示例有效,并且最容易一目了然。真的有必要改进吗?

于 2009-07-28T19:20:44.437 回答
0

IfNotNull 解决方案是最好的(直到 C# 团队为我们提供了一个空安全解引用运算符,也就是说)。

于 2009-07-28T22:13:08.067 回答
0

我对这两种解决方案都不太感兴趣。原版的较短版本有什么问题:

string activeControlName = null;
if (Form.ActiveForm != null)
    if (Form.ActiveForm.ActivControl != null) activeControlname = activeControl.Name;

如果不是这样,那么我会考虑编写一个 NotNullChain 或 FluentNotNull 对象,而不是连续链接几个非空测试。我同意作用于 null 的 IfNotNull 扩展方法似乎有点奇怪——即使扩展方法只是语法糖。

我认为 Mark Synowiec 的回答可能是通用的。

恕我直言,我认为 C# 核心团队应该关注这个“问题”,尽管我认为还有更大的事情需要解决。

于 2010-06-23T03:36:42.190 回答
0

当然,原始的 2-nested IF 比其他选择更具可读性。但是建议您更一般地解决问题,这是另一种解决方案:

try
{
    var activeForm = Form.ActiveForm; assumeIsNotNull(activeForm);
    var activeControl = activeForm.ActiveControl; assumeIsNotNull(activeControl);
    var activeControlname = activeControl.Name;
}
catch (AssumptionChainFailed)
{
}

在哪里

class AssumptionChainFailed : Exception { }
void assumeIsNotNull(object obj)
{
    if (obj == null) throw new AssumptionChainFailed();
}
于 2012-02-27T18:27:28.670 回答