在C#中恶意使用Maybe monad和扩展方法?

Jud*_*ngo 35 c# monads extension-methods

编辑2015这个问题及其答案已不再适用.它在C#6出现之前被问到,它具有空传播的opertor(?.),它避免了在这个问题和随后的答案中讨论的hacky-workarounds.截至2015年,在C#中,您现在应该使用Form.ActiveForm?.ActiveControl?.Name.


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

尝试#1常用代码:

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

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;
}
Run Code Online (Sandbox Code Playgroud)

我认为这更好,然而,重复的"IfNotNull"和lambdas会有一些语法混乱.我现在正在考虑这个设计:

尝试使用扩展方法#3,可能<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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Dan*_*ker 18

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

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

我的(不知道上述内容):C#中的管道转发

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

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

  1. 有些人坚持认为,如果扩展方法的this参数是,则应该抛出异常null.如果方法名称清楚,我不同意.

  2. 适用范围太广的扩展程序会使自动完成菜单变得混乱.这可以通过正确使用命名空间来避免,因此它们不会惹恼那些不想要它们的人.

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

我最终得到了一个简单的自包含Maybe类,它有一个静态方法(不是扩展方法),对我来说效果非常好.但是,我和一个小团队一起工作,我的下一个最资深的同事对函数式编程和lambdas等感兴趣,所以他不会被它推迟.


Jon*_*eet 14

就像我是扩展方法的粉丝一样,我认为这不是真的有用.你仍然重复了表达式(在monadic版本中),它只是意味着你必须Maybe向每个人解释.在这种情况下,增加的学习曲线似乎没有足够的好处.

IfNotNull版本至少设法避免重复,但我认为它仍然只是有点太长,没有实际更清楚.

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


另外,我最喜欢的半邪恶扩展方法是:

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

这让你转过来:

void Foo(string x, string y)
{
    if (x == null)
    {
        throw new ArgumentNullException(nameof(x));
    }
    if (y == null)
    {
        throw new ArgumentNullException(nameof(y));
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

成:

void Foo(string x, string y)
{
    x.ThrowIfNull(nameof(x));
    y.ThrowIfNull(nameof(y));
    ...
}
Run Code Online (Sandbox Code Playgroud)

仍然有令人讨厌的重复参数名称,但至少它更整洁.当然,在.NET 4.0中我会使用Code Contracts,这就是我现在要写的内容...... Stack Overflow是一个很好的避免工作;)