C#的属性销售操作员


9

c#中的null-coalescing运算符使您可以缩短代码

  if (_mywidget == null)
     return new Widget();
  else
     return _mywidget;

向下:

  return _mywidget ?? new Widget();

我一直发现,我想在C#中拥有一个有用的运算符,它将允许您返回对象的属性,或者如果对象为null则返回其他值。所以我想替换

  if (_mywidget == null)
     return 5;
  else
     return _mywidget.Length;

带有:

  return _mywidget.Length ??! 5;

我不禁想到该操作符不存在一定的原因。它有代码味吗?有没有更好的方法可以写这个?(我知道空对象模式,但是用它来替换这四行代码似乎有点过分。)


1
条件运算符在这里足够吗?
Anon。

1
有人写了一些东西,使您可以执行以下操作:字符串location = employee.office.address.location?“未知”; 。当对象之一(employeeofficeaddresslocation)为空时,会将位置设置为“未知”。不幸的是,我不记得是谁写的或者他在哪里发布的。如果再次找到它,将在这里发布!
克里斯托夫·克拉斯

1
这个问题将在StackOverflow上获得更多关注。
Job

2
??!是C ++中的运算符。:-)
James McNellis 2011年

3
您好,2011年,刚刚打电话说c#正在获得安全的导航操作员
Nathan Cooper

Answers:


5

我非常想要一种C#语言功能,该功能可以让我安全地执行x.Prop.SomeOtherProp.ThirdProp,而不必检查每个null。在一个我必须做大量“深度属性遍历”的代码库中,我编写了一个名为Navigate的扩展方法,该方法吞噬了NullReferenceExceptions,而返回了默认值。所以我可以做这样的事情:

var propVal = myThing.Navigate(x => x.PropOne.PropTwo.PropThree.PropFour, defaultValue);

不利的一面是它具有不同的代码味道:吞咽的异常。如果您想执行类似“正确”的操作,则可以将lamdba用作表达式,然后即时修改表达式以在每个属性访问器周围添加null检查。我追求“快速又肮脏”,却没有以“更好”的方式实现它。但是也许当我有一些多余的思考周期时,我会重新审视并将其发布到某个地方。

为了更直接地回答您的问题,我猜测这不是功能的原因是因为实现该功能的成本超过了拥有该功能所带来的收益。C#团队必须选择要关注的功能,而这个功能还没有浮出水面。只是一个猜测,尽管我没有任何内部消息。


1
确实是我的想法,但是我猜这种安全方法的性能会很糟糕(由于许多Expression.Compile())...太糟糕了,它没有在C#中实现(类似Obj.?PropA.?Prop1)
Guillaume86

x.PropOne.PropTwo.PropThree.PropFour是一个糟糕的设计选择,因为它违反了Demeter定律。重新设计您的方法/类,以便您不需要使用它。
贾斯汀·希尔德

根据Demeter律不顾后果地避开深入的财产访问权,就像盲目地规范数据库一样危险。你可以走得太远。
2014年

3
在C#6.0中,可以通过使用Null条件运算符(也称为Elvis运算符)msdn.microsoft.com/en-us/magazine/dn802602.aspx
victorvartan 2014年

15

我相信您现在就可以使用C#6轻松做到这一点:

return _mywidget?.Length ?? 5;

请注意?.左侧的空条件运算符。_mywidget?.Length如果_mywidget为null,则返回null。

但是,正如其他人所建议的那样,简单的三元运算符可能更易于阅读。


9

只是关于他们为什么不这样做的猜测。毕竟,我不相信?是第一个C#版本。

无论如何,我只会使用条件运算符并将其称为一天:

return (_mywidget != null) ? _mywidget.Length : 5;

?? 只是条件运算符的捷径。


5
就个人而言,这与让操作员首先执行此操作一样糟糕(恕我直言)。您想要某个对象中的某些东西...那个对象可能不存在...所以让我们5作为答案,以防它不存在。开始要求特殊操作人员之前,您需要先查看设计。
Moo-Juice

1
@ Moo-Juice我只是想像一下维护此代码的人,“为什么给我5个?嗯 什么?!'
msarchet 2011年

4

看起来就像三元运算符:

return (_mywidget != NULL) ? _mywidget : new Widget();

3

我个人认为这取决于可读性。在第一个示例中,??翻译很好地表示为“您在吗?不,我们来做”。

在第二个示例中,??!显然是WTF,对程序员没有任何意义。...对象不存在,所以我无法获得该属性,因此我将返回5。这甚至意味着什么?什么是5?我们如何得出5是一个好值的结论?

简而言之,第一个示例是有意义的……不存在,而是新的。在第二个中,有争议。


2
好吧,您必须忽略以下事实!不存在。想象一下??!是很常见的,如果使用了它,则根据此来做决定。
whatsisname 2011年

3
这让我想起了C ++中所谓的“ WTF运算符”(这实际上起作用:(foo() != ERROR)??!??! cerr << "Error occurred" << endl;。)
TamásSzelei 2011年

@whatsisname,它更多地反映了这样一个事实,即它适合于所做出的决定。创建一个不存在的对象是有意义的。分配一个对读者没有任何意义的任意值,因为您无法获得某物的属性,这确实是WTF场景。
Moo-Juice

我认为您正在陷入我的榜样。也许0优于5。也许该属性是一个对象,所以如果_mywidget为null,则想返回一个新的foodle()。我完全不同意?比??更具可读性!虽然:)
Ben Fulton

@Ben,尽管我同意专注于运算符本身并不能解决您的问题,但我还是将其与程序员的看法相提并论5。我确实认为,还有更多的问题需要执行类似的操作,而在第一个示例中,需求是显而易见的,尤其是在诸如缓存管理器之类的事情中。
Moo-Juice

2

我认为人们对您返回的“ 5”太着迷了……也许0会更好:)

无论如何,我认为问题在于,??!它实际上不是独立的运算符。考虑一下这意味着什么:

var s = myString ??! "";

在这种情况下,这是没有意义的:仅当左侧操作数是属性访问器时才有意义。还是这样:

var s = Foo(myWidget.Length) ??! 0;

那里有一个属性访问器,但我仍然不认为这有意义(如果myWidgetnull,这是否意味着我们根本不打电话Foo()?),或者这仅仅是一个错误?

我认为问题在于,它在语言中的适应性??并不强。


1

有人创建了一个实用程序类来为您完成此任务。但是我找不到。我发现在MSDN论坛上有类似的发现(请看第二个答案)。

只需做一些工作,就可以扩展它,以评估该示例不支持的方法调用和其他表达式。您也可以扩展它以接受默认值。


1

我知道你来自哪里。似乎每个人都通过您提供的示例将其包裹在轴周围。虽然我同意魔术数不是一个好主意,但对于某些属性,它会等同于使用空合并运算符。

例如,您有一些对象绑定到地图,并且希望它们处于适当的高度。DTED地图的数据中可能有孔,因此可能有一个空值,以及介于-100到〜8900米范围内的值。您可能需要一些类似的东西:

mapObject.Altitude = mapObject.Coordinates.DtedAltitude ?? DEFAULT_ALTITUDE;

在这种情况下,如果尚未设置坐标,或者坐标对象无法加载该位置的DTED数据,则空合并操作符将填充默认高度。

我认为这是非常有价值的,但是我对为什么不这样做的猜测仅限于编译器的复杂性。在某些情况下,行为可能无法预期。


1

当不处理深度嵌套时,我已经使用了此方法(在C#6中引入空合并运算符之前)。对我来说,这很清楚,但这也许是因为我已经习惯了,其他人可能会感到困惑。

return ( _mywidget ?? new MyWidget() {length = defaultLength}).Length;

当然,这并不总是适用的,因为有时您不能直接设置需要访问的属性,或者当对象本身的构造非常昂贵时(除其他原因外)。

另一种选择是使用NullObject模式。您可以使用合理的默认值定义该类的单个静态实例,然后改用它。

return ( _mywidget ?? myWidget.NullInstance).Length;

0

魔术数字通常是难闻的气味。如果5是任意数字,则最好将其拼写清楚,以便更清楚地记录下来。我认为有一个例外情况是,如果您有一个在特定上下文中充当默认值的值的集合。

如果对象具有一组默认值,则可以将其子类化以创建默认的单例实例。

类似于:return(myobj ?? default_obj).Length


0

Length属性通常是一个值类型,因此不能为null,因此null-coalescing运算符在这里没有意义。

但是,您可以使用引用类型的属性来执行此操作,例如: var window = App.Current.MainWindow ?? new Window();


该示例试图考虑如果Current为空,在您的情况下会发生什么。您的示例将崩溃……但是拥有一个可以使误导链中任何位置的合并无效的运算符将很方便。
2014年

-1

我以前见过一个有类似想法的人,但是在大多数情况下,您还希望将新值分配给null字段,例如:

return _obj ?? (_obj = new Class());

因此,想法是将??=分配为:

return _obj ??= new Class();

我认为这比使用感叹号更有意义。

这个想法不是我的,但我真的很喜欢:)


1
您是这个榜样的受害者吗?您的示例可以简化return _obj ?? new Class();??=此处不需要。
马特·艾伦

@Matt Ellen,你理解错了。该作业是有意的。我建议另一种选择。它并不完全符合OP的要求,但我相信它将同样有用。
chakrit 2011年

2
如果要返回值,为什么要分配?
马特·艾伦

延迟初始化?
chakrit 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.