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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1196031/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 10:35:31  来源:igfitidea点击:

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

c#extension-methodsmonads

提问by Judah Gabriel Himango

edit 2015This question and its answers are no longer relevant. 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. As of 2015, in C# you should now use Form.ActiveForm?.ActiveControl?.Name.

编辑 2015这个问题及其答案不再相关。它是在 C# 6 出现之前被问到的,它具有空传播运算符 (?.),它避免了在这个问题和后续答案中讨论的 hacky-workarounds。截至 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 中的 null 传播问题,这通常会导致丑陋、重复的代码,如下所示:

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. I'm now considering this design:

我认为这更好,但是,重复的“IfNotNull”和 lambdas 存在一些语法混乱。我现在正在考虑这样的设计:

Attempt #3, Maybe<T> with extension method

尝试 #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);
    }
}

My question is: is this an evil abuse of extension methods? Is it better than the old usual null checks?

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

采纳答案by Daniel Earwicker

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 表达式中的空值?

There are a couple of reasons why the IfNotNullextension method may be unpopular.

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

  1. Some people are adamant that an extension method should throw an exception if its thisparameter is 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.

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

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

I've played around with the IEnumerableapproach 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 IfNotNullchaining or the raw imperative code.

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

I've ended up with a simple self-contained Maybeclass with one static method (not an extension method) and that works very nicely for me. 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.

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

回答by Jon Skeet

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 Maybeto everyone. The added learning curve doesn't seem to have enough benefit in this case.

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

The IfNotNullversion 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 中,我会使用代码契约,这就是我现在要写的内容......堆栈溢出是避免工作的好方法;)

回答by Mark Synowiec

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) :)

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

回答by HeathenWorld

The initial sample works and is the easiest to read at a glance. Is there really a need to improve on that?

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

回答by jeroenh

The IfNotNull solution is the best (until the C# team gives us a null-safe dereferencing operator, that is).

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

回答by BuddyJoe

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. I agree that the IfNotNull extension method acting on a null seems a little weird - even though extension methods are just syntactic sugar.

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

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# 核心团队应该考虑这个“问题”,尽管我认为还有更大的事情需要解决。

回答by danbst

Sure, original 2-nested IF is much more readable than other choices. But suggesting you want to solve problem more generally, here is another solution:

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

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();
}

回答by thefellow3j

In case you're dealing with C# 6.0/VS 2015 and above, they now have a built-in solution for null propagation:

如果您使用的是 C# 6.0/VS 2015 及更高版本,它们现在有一个用于 null 传播的内置解决方案:

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