简体   繁体   English

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

[英]Evil use of Maybe monad and extension methods in C#?

edit 2015 This question and its answers are no longer relevant. 编辑2015此问题及其答案不再相关。 It was asked before the advent of C# 6, which has the null propagating opertor (?.), which obviates the hacky-workarounds discussed in this question and subsequent answers. 在C#6问世之前,有人问过这个问题,它有一个无效的传播运算符(?。),它消除了此问题和后续答案中讨论的hacky解决方法。 As of 2015, in C# you should now use Form.ActiveForm?.ActiveControl?.Name. 从2015年开始,在C#中,您现在应该使用Form.ActiveForm?.ActiveControl?.Name。


I've been thinking about the null propagation problem in .NET, which often leads to ugly, repeated code like this: 我一直在考虑.NET中的空传播问题,该问题通常会导致类似这样的难看的重复代码:

Attempt #1 usual code: 尝试#1常规代码:

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

There have been a few discussions on StackOverflow about a Maybe<T> monad, or using some kind of "if not null" extension method: 在StackOverflow上已经有一些关于Maybe <T> monad的讨论,或者使用某种“ if not null”扩展方法:

Attempt #2, extension method: 尝试#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;
}

I think this is better, however, there's a bit of syntactic messy-ness with the repeated "IfNotNull" and the lambdas. 我认为这样做更好,但是,重复的“ IfNotNull”和lambda有点句法混乱。 I'm now considering this design: 我现在正在考虑这种设计:

Attempt #3, Maybe<T> with extension method 尝试#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);
    }
}

My question is : is this an evil abuse of extension methods? 我的问题是 :这是否滥用扩展方法? Is it better than the old usual null checks? 它比以前的常规null检查好吗?

It's interesting that so many people independently pick the name IfNotNull , for this in C# - it must be the most sensible name possible! 有趣的是,有这么多人独立选择名称IfNotNull ,为此在C#中-它必须是最明智的名称! :) :)

Earliest one I've found on SO: Possible pitfalls of using this (extension method based) shorthand 我在SO上找到的最早的一个: 使用此(基于扩展方法)速记的可能的陷阱

My one (in ignorance of the above): Pipe forwards in C# 我的一个(不了解上述内容): 用C#进行管道转发

Another more recent example: How to check for nulls in a deep lambda expression? 另一个最近的示例: 如何在深lambda表达式中检查null?

There are a couple of reasons why the IfNotNull extension method may be unpopular. IfNotNull扩展方法可能不受欢迎的原因有两个。

  1. Some people are adamant that an extension method should throw an exception if its this parameter is null . 有些人坚持认为,如果扩展方法的this参数为null则该方法应引发异常。 I disagree if the method name makes it clear. 我不同意方法名称是否明确。

  2. Extensions that apply too broadly will tend to clutter up the auto-completion menu. 应用范围太广的扩展会导致自动完成菜单混乱。 This can be avoided by proper use of namespaces so they don't annoy people who don't want them, however. 可以通过正确使用命名空间来避免这种情况,这样它们就不会惹恼那些不想要它们的人。

I've played around with the IEnumerable approach also, just as an experiment to see how many things I could twist to fit the Linq keywords, but I think the end result is less readable than either the IfNotNull chaining or the raw imperative code. 我还尝试了IEnumerable方法,就像一个实验来看看我可以扭曲多少东西以适合Linq关键字,但是我认为最终结果比IfNotNull链接或原始命令性代码可读性差。

I've ended up with a simple self-contained Maybe class with one static method (not an extension method) and that works very nicely for me. 我最终得到了一个简单的自包含Maybe类,它带有一个静态方法(不是扩展方法),对我来说非常好用。 But then, I work with a small team, and my next most senior colleague is interested in functional programming and lambdas and so on, so he isn't put off by it. 但是,然后,我与一个小团队一起工作,而我的下一个最高级的同事对函数式编程和lambda等感兴趣,所以他并没有因此而感到失望。

Much as I'm a fan of extension methods, I don't think this is really helpful. 我非常喜欢扩展方法,但我认为这真的没有帮助。 You've still got the repetition of the expressions (in the monadic version), and it just means that you've got to explain Maybe to everyone. 您仍然可以重复这些表达式(在单声道版本中),这仅意味着您必须向所有人解释Maybe The added learning curve doesn't seem to have enough benefit in this case. 在这种情况下,增加的学习曲线似乎没有足够的好处。

The IfNotNull version at least manages to avoid the repetition, but I think it's still just a bit too longwinded without actually being clearer. IfNotNull版本至少设法避免了重复,但是我认为它仍然有点太冗长了,实际上并没有变得更加清晰。

Maybe one day we'll get a null-safe dereferencing operator... 也许有一天我们会得到一个空安全的解引用运算符...


Just as an aside, my favourite semi-evil extension method is: 顺便说一句,我最喜欢的半邪恶扩展方法是:

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

That lets you turn this: 这使您可以打开此菜单:

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

into: 变成:

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

There's still the nasty repetition of the parameter name, but at least it's tidier. 参数名称仍然令人讨厌,但至少更整洁。 Of course, in .NET 4.0 I'd use Code Contracts, which is what I'm meant to be writing about right now... Stack Overflow is great work avoidance ;) 当然,在.NET 4.0中,我将使用代码契约,这就是我现在要写的内容……堆栈溢出是避免工作的好方法;)

If you want an extension method to reduce the nested if's like you have, you might try something like this: 如果您想使用扩展方法来减少嵌套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;
}

so in your code you'd just do: 因此,在您的代码中,您只需执行以下操作:

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

I don't know if I'd want to use it to often due to the slowness of reflection, and I don't really think this much better than the alternative, but it should work, regardless of whether you hit a null along the way... 我不知道是否由于反射缓慢而经常使用它,并且我真的不认为这比替代方法要好得多,但是不管您是否沿反射方向打零,它都应该起作用。道路...

(Note: I might've gotten those types mixed up) :) (注意:我可能将这些类型混淆了):)

如果您使用的是C#6.0 / VS 2015及更高版本,则它们现在具有针对空传播的内置解决方案:

string ans = nullableString?.Length.ToString(); // null if nullableString == null, otherwise the number of characters as a string.

The initial sample works and is the easiest to read at a glance. 初始样本有效,一目了然。 Is there really a need to improve on that? 真的需要对此进行改进吗?

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

I'm not too crazy about either solution. 我对这两种解决方案都不太疯狂。 What was wrong with ashorter version of the original: 较短版本的原始版本出了什么问题:

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

If not this, then I would look at writing a NotNullChain or FluentNotNull object than can chain a few not null tests in a row. 如果不是这样,那么我将看写一个NotNullChain或FluentNotNull对象,它可以连续链接一些非null测试。 I agree that the IfNotNull extension method acting on a null seems a little weird - even though extension methods are just syntactic sugar. 我同意,作用于null的IfNotNull扩展方法似乎有些怪异-即使扩展方法只是语法糖。

I think Mark Synowiec's answer might be able to made generic. 我认为Mark Synowiec的答案也许可以通用。

IMHO, I think the C# core team should look at the this "issue", although I think there are bigger things to tackle. 恕我直言,我认为C#核心团队应该研究这个“问题”,尽管我认为还有很多事情需要解决。

Sure, original 2-nested IF is much more readable than other choices. 当然,原始的2个嵌套IF比其他选择更具可读性。 But suggesting you want to solve problem more generally, here is another solution: 但是建议您更一般地解决问题,这是另一种解决方案:

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

where 哪里

class AssumptionChainFailed : Exception { }
void assumeIsNotNull(object obj)
{
    if (obj == null) throw new AssumptionChainFailed();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM