在 C# 中恶意使用 Maybe monad 和扩展方法?
编辑 2015 此问题及其答案不再相关。 在 C# 6 出现之前就有人提出这个问题,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,也许
// 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);
}
}
我的问题是:这是对扩展方法的邪恶滥用吗? 它比旧的通常的空检查更好吗?
edit 2015 This 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.
I've been thinking about the null propagation problem in .NET, which often leads to ugly, repeated code like this:
Attempt #1 usual code:
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:
Attempt #2, extension method:
// 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:
Attempt #3, Maybe<T> with extension method
// 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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
有趣的是,这么多人独立选择了名称
IfNotNull
,在 C# 中 - 它一定是最明智的名称! :)我在 SO 上发现的最早的一个:可能的陷阱使用这个(基于扩展方法的)简写
我的简写(不了解上述内容):C# 中的管道转发
另一个更新的示例:如何检查深层 lambda 表达式中的 null 值?
IfNotNull
扩展方法可能不受欢迎的原因有几个。有些人坚持认为,如果扩展方法的
this
参数为null
,则该扩展方法应该抛出异常。 我不同意方法名称是否清楚。应用范围太广的扩展往往会使自动完成菜单变得混乱。 但是,可以通过正确使用命名空间来避免这种情况,这样它们就不会惹恼那些不需要它们的人。
我也尝试过 IEnumerable 方法,只是作为一个实验,看看我可以扭曲多少东西来适应 Linq 关键字,但我认为最终结果的可读性不如
IfNotNull
链接或原始命令式代码。我最终得到了一个简单的自包含的
Maybe
类,其中包含一个静态方法(不是扩展方法),这对我来说非常有效。 但是,我和一个小团队一起工作,我的下一位最资深的同事对函数式编程和 lambda 等感兴趣,所以他并没有因此而推迟。It's interesting that so many people independently pick the name
IfNotNull
, for this in C# - it must be the most sensible name possible! :)Earliest one I've found on SO: Possible pitfalls of using this (extension method based) shorthand
My one (in ignorance of the above): Pipe forwards in C#
Another more recent example: How to check for nulls in a deep lambda expression?
There are a couple of reasons why the
IfNotNull
extension method may be unpopular.Some people are adamant that an extension method should throw an exception if its
this
parameter isnull
. I disagree if the method name makes it clear.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 theIfNotNull
chaining or the raw imperative code.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. 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.尽管我很喜欢扩展方法,但我认为这并没有真正的帮助。 你仍然会重复表达式(在单子版本中),这只是意味着你必须向每个人解释
也许
。 在这种情况下,增加的学习曲线似乎没有足够的好处。IfNotNull
版本至少设法避免了重复,但我认为它仍然有点过于冗长,实际上并没有更清晰。也许有一天我们会得到一个空安全解引用运算符...
顺便说一句,我最喜欢的半邪恶扩展方法是:
这可以让你把这个:
变成:
参数名称仍然有令人讨厌的重复,但是在至少它更整洁。 当然,在 .NET 4.0 中我会使用代码契约,这就是我现在要写的内容...... Stack Overflow 是很好的避免工作的方法;)
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. 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.Maybe one day we'll get a null-safe dereferencing operator...
Just as an aside, my favourite semi-evil extension method is:
That lets you turn this:
into:
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 ;)
如果您想要一个扩展方法来减少嵌套的 if 就像您所拥有的那样,您可以尝试这样的事情:
所以在您的代码中您只需这样做:
我不知道我是否想经常使用它,因为反射缓慢,我并不认为这比替代方案好多少,但它应该有效,无论您是否一路上遇到空值......
(注意:我可能已经将这些类型混淆了): )
If you want an extension method to reduce the nested if's like you have, you might try something like this:
so in your code you'd just do:
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 及更高版本,它们现在有一个用于 null 传播的内置解决方案:
In case you're dealing with C# 6.0/VS 2015 and above, they now have a built-in solution for null propagation:
最初的示例有效,并且最容易一目了然。 真的需要改进吗?
The initial sample works and is the easiest to read at a glance. Is there really a need to improve on that?
IfNotNull 解决方案是最好的(直到 C# 团队为我们提供了空安全解引用运算符)。
The IfNotNull solution is the best (until the C# team gives us a null-safe dereferencing operator, that is).
我对这两种解决方案都不太着迷。 原始版本的较短版本有什么问题:
如果不是这个,那么我会考虑编写一个 NotNullChain 或 FluentNotNull 对象,而不是可以连续链接一些非空测试。 我同意作用于 null 的 IfNotNull 扩展方法似乎有点奇怪 - 尽管扩展方法只是语法糖。
我认为 Mark Synowiec 的答案也许可以通用。
恕我直言,我认为 C# 核心团队应该考虑这个“问题”,尽管我认为还有更大的事情需要解决。
I'm not too crazy about either solution. What was wrong with ashorter version of the original:
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.
I think Mark Synowiec's answer might be able to made generic.
IMHO, I think the C# core team should look at the this "issue", although I think there are bigger things to tackle.
当然,原始的 2 嵌套 IF 比其他选择更具可读性。 但建议您想更普遍地解决问题,这是另一种解决方案
:
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:
where