使用这种(基于扩展方法)速记的可能的缺陷

C#6更新

在C#6中?. 现在是一个语言function :

 // C#1-5 propertyValue1 = myObject != null ? myObject.StringProperty : null; // C#6 propertyValue1 = myObject?.StringProperty; 

下面的问题仍然适用于旧版本,但如果开发一个新的应用程序使用新的?. 操作者是更好的做法。

原问题:

我经常要访问可能的空对象的属性:

 string propertyValue1 = null; if( myObject1 != null ) propertyValue1 = myObject1.StringProperty; int propertyValue2 = 0; if( myObject2 != null ) propertyValue2 = myObject2.IntProperty; 

等等…

我经常使用它,我有一个片段。

如果符合以下条件,您可以在一定程度上缩小这个范围:

 propertyValue1 = myObject != null ? myObject.StringProperty : null; 

然而,这有点笨重,特别是如果设置很多属性,或者如果多个级别可以为空,例如:

 propertyValue1 = myObject != null ? (myObject.ObjectProp != null ? myObject.ObjectProp.StringProperty) : null : null; 

我真正想要的是?? 风格的语法,这对于直接为null的types非常有用:

 int? i = SomeFunctionWhichMightReturnNull(); propertyValue2 = i ?? 0; 

所以我想出了以下几点:

 public static TResult IfNotNull<T, TResult>( this T input, Func<T, TResult> action, TResult valueIfNull ) where T : class { if ( input != null ) return action( input ); else return valueIfNull; } //lets us have a null default if the type is nullable public static TResult IfNotNull<T, TResult>( this T input, Func<T, TResult> action ) where T : class where TResult : class { return input.IfNotNull( action, null ); } 

这让我们知道这个语法:

 propertyValue1 = myObject1.IfNotNull( x => x.StringProperty ); propertyValue2 = myObject2.IfNotNull( x => x.IntProperty, 0); //or one with multiple levels propertyValue1 = myObject.IfNotNull( o => o.ObjectProp.IfNotNull( p => p.StringProperty ) ); 

这简化了这些调用,但我不确定检查这种types的扩展方法 – 它确实使代码更容易阅读,但代价是扩展对象。 这将出现在一切,虽然我可以把它放在一个专门引用的命名空间。

这个例子是一个相当简单的例子,稍微复杂一点就是比较两个可为空的对象属性:

 if( ( obj1 == null && obj2 == null ) || ( obj1 != null && obj2 != null && obj1.Property == obj2.Property ) ) ... //becomes if( obj1.NullCompare( obj2, (x,y) => x.Property == y.Property ) ... 

以这种方式使用扩展的缺陷是什么? 其他编码人员可能会感到困惑吗? 这只是滥用扩展?


我想我在这里真正想要的是一个编译器/语言扩展:

 propertyValue1 = myObject != null ? myObject.StringProperty : null; //becomes propertyValue1 = myObject?StringProperty; 

这将使复杂的情况更容易:

 propertyValue1 = myObject != null ? (myObject.ObjectProp != null ? myObject.ObjectProp.StringProperty) : null //becomes propertyValue1 = myObject?ObjectProp?StringProperty; 

这只适用于值types,但您可以返回可为空的等价物:

 int? propertyValue2 = myObject?ObjectProp?IntProperty; //or int propertyValue3 = myObject?ObjectProp?IntProperty ?? 0; 

我们独立地提出了完全相同的扩展方法名称和实现: 空传播扩展方法 。 所以我们不认为这是混淆或滥用扩展方法。

我会用链接编写你的“多层次”的例子如下:

 propertyValue1 = myObject.IfNotNull(o => o.ObjectProp).IfNotNull(p => p.StringProperty); 

Microsoft Connect提供了“?”的一个现在closures的错误 。 作为一个新的C#操作符来执行这个空传播。 Mads Torgersen(来自C#语言团队)简要解释了为什么他们不会实现它。

这里是链接成员的另一个解决scheme,包括扩展方法:

 public static U PropagateNulls<T,U> ( this T obj ,Expression<Func<T,U>> expr) { if (obj==null) return default(U); //uses a stack to reverse Member1(Member2(obj)) to obj.Member1.Member2 var members = new Stack<MemberInfo>(); bool searchingForMembers = true; Expression currentExpression = expr.Body; while (searchingForMembers) switch (currentExpression.NodeType) { case ExpressionType.Parameter: searchingForMembers = false; break; case ExpressionType.MemberAccess: { var ma= (MemberExpression) currentExpression; members.Push(ma.Member); currentExpression = ma.Expression; } break; case ExpressionType.Call: { var mc = (MethodCallExpression) currentExpression; members.Push(mc.Method); //only supports 1-arg static methods and 0-arg instance methods if ( (mc.Method.IsStatic && mc.Arguments.Count == 1) || (mc.Arguments.Count == 0)) { currentExpression = mc.Method.IsStatic ? mc.Arguments[0] : mc.Object; break; } throw new NotSupportedException(mc.Method+" is not supported"); } default: throw new NotSupportedException (currentExpression.GetType()+" not supported"); } object currValue = obj; while(members.Count > 0) { var m = members.Pop(); switch(m.MemberType) { case MemberTypes.Field: currValue = ((FieldInfo) m).GetValue(currValue); break; case MemberTypes.Method: var method = (MethodBase) m; currValue = method.IsStatic ? method.Invoke(null,new[]{currValue}) : method.Invoke(currValue,null); break; case MemberTypes.Property: var method = ((PropertyInfo) m).GetGetMethod(true); currValue = method.Invoke(currValue,null); break; } if (currValue==null) return default(U); } return (U) currValue; } 

那么你可以做到这一点,任何可以为空,或没有:

 foo.PropagateNulls(x => x.ExtensionMethod().Property.Field.Method()); 

如果您发现自己必须经常检查一个对象的引用是否为空,可能是您应该使用空对象模式 。 在这种模式中,不是使用null来处理没有对象的情况,而是使用相同的接口实现一个新的类,但是方法和属性返回足够的默认值。

怎么

 propertyValue1 = myObject.IfNotNull(o => o.ObjectProp.IfNotNull( p => p.StringProperty ) ); 

比读书更容易

 if(myObject != null && myObject.ObjectProp != null) propertyValue1 = myObject.ObjectProp.StringProperty; 

Jafar Husain发布了一个使用Expression Trees的示例来检查链中的null ,C#3中的运行时macros 。

这显然有性能影响。 现在,只要我们有办法在编译时做到这一点。

我只能说,我喜欢这个黑客!

我没有意识到,扩展方法并不意味着一个空检查,但它是完全有道理的。 正如詹姆斯指出的那样,扩展方法调用本身并不比普通的方法更昂贵,但是如果你正在做大量的工作,那么遵循空值对象模式是有意义的。 或者使用空对象和? 一起。

 class Class1 { public static readonly Class1 Empty = new Class1(); . . x = (obj1 ?? Class1.Empty).X; 

它确实使代码更容易阅读,但是以扩展对象为代价。 这会出现在一切,

请注意,你实际上并没有扩展任何东西(理论上除外)。

 propertyValue2 = myObject2.IfNotNull( x => x.IntProperty, 0); 

将会生成IL代码,就像写入代码一样:

 ExtentionClass::IfNotNull(myObject2, x => x.IntProperty, 0); 

没有“开销”添加到对象来支持这一点。

对于读者不知道它看起来像你正在调用空引用的方法。 如果你想这样做,我build议把它放在一个实用程序类,而不是使用扩展方法:

 propertyValue1 = Util.IfNotNull(myObject1, x => x.StringProperty ); propertyValue2 = Util.IfNotNull(myObject2, x => x.IntProperty, 0); 

“Util”。 格拉兹,但IMO是较小的句法邪恶。

另外,如果你把这个作为一个团队的一部分来开发,那么就要问问他人的想法和行为。 在常用模式的代码库中保持一致性非常重要。

虽然扩展方法通常会在从null实例中调用时引起误解,但我认为在这种情况下,意图是非常简单的

 string x = null; int len = x.IfNotNull(y => y.Length, 0); 

我想确保这个静态方法工作的值types可以为空,如int?

编辑:编译器说,这些都是无效的:

  public void Test() { int? x = null; int a = x.IfNotNull(z => z.Value + 1, 3); int b = x.IfNotNull(z => z.Value + 1); } 

除此之外,去做吧。

不是一个问题的答案,但在C#6.0中 有空条件运算符 。 我可以争辩说,从C#6.0开始,在OP中使用这个选项将是一个糟糕的select:)

所以你的表情比较简单

 string propertyValue = myObject?.StringProperty; 

如果myObject为null,则返回null。 如果该属性是一个值types,你必须使用等价的可空types,

 int? propertyValue = myObject?.IntProperty; 

否则,您可以与空合并运算符合并,以在null为空时提供默认值。 例如,

 int propertyValue = myObject?.IntProperty ?? 0; 

?. 不是唯一可用的语法。 对于索引属性,您可以使用?[..] 。 例如,

 string propertyValue = myObject?[index]; //returns null in case myObject is null 

一个令人惊讶的行为?. 运算符是它可以智能地绕过随后的.Member调用,如果对象发生空。 链接中给出了一个这样的例子:

 var result = value?.Substring(0, Math.Min(value.Length, length)).PadRight(length); 

在这种情况下,如果value为null且value.Lengthexpression式不会导致NullReferenceExceptionresult为null。

就个人而言,即使你所有的解释,我不记得这是如何工作:

 if( obj1.NullCompare( obj2, (x,y) => x.Property == y.Property ) 

这可能是因为我没有C#经验; 不过,我可以阅读并理解代码中的其他内容。 我更喜欢保持代码语言不可知(特别是对于微不足道的东西),以便明天另一个开发人员可以将其改为一种全新的语言,而不需要太多关于现有语言的信息。

这里是使用myObject.NullSafe(x => x.SomeProperty.NullSafe(x => x.SomeMethod))的另一个解决scheme,在http://www.epitka.blogspot.com/上解释;