Kei*_*ith 15 c# extension-methods .net-3.5
C#6更新
// C#1-5
propertyValue1 = myObject != null ? myObject.StringProperty : null;
// C#6
propertyValue1 = myObject?.StringProperty;
Run Code Online (Sandbox Code Playgroud)
下面的问题仍适用于旧版本,但如果使用new ?.运算符开发新应用程序则更好.
原始问题:
我经常想要访问可能为null的对象的属性:
string propertyValue1 = null;
if( myObject1 != null )
propertyValue1 = myObject1.StringProperty;
int propertyValue2 = 0;
if( myObject2 != null )
propertyValue2 = myObject2.IntProperty;
Run Code Online (Sandbox Code Playgroud)
等等...
我经常使用它,因此我有一个代码片段.
如果符合以下条件,您可以在某种程度上缩短此内容:
propertyValue1 = myObject != null ? myObject.StringProperty : null;
Run Code Online (Sandbox Code Playgroud)
然而,这有点笨拙,特别是如果设置大量属性或多个级别可以为null,例如:
propertyValue1 = myObject != null ?
(myObject.ObjectProp != null ? myObject.ObjectProp.StringProperty) : null : null;
Run Code Online (Sandbox Code Playgroud)
我真正想要的是??样式语法,它适用于直接空类型:
int? i = SomeFunctionWhichMightReturnNull();
propertyValue2 = i ?? 0;
Run Code Online (Sandbox Code Playgroud)
所以我想出了以下内容:
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 ); }
Run Code Online (Sandbox Code Playgroud)
这让我用这个语法:
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 ) );
Run Code Online (Sandbox Code Playgroud)
这简化了这些调用,但我不确定是否检查了这种扩展方法 - 它确实使代码更容易阅读,但代价是扩展对象.虽然我可以将它放在一个专门引用的命名空间中,但它会出现在所有内容中.
这个例子很简单,比较两个可以为空的对象属性稍微复杂一点:
if( ( obj1 == null && obj2 == null ) ||
( obj1 != null && obj2 != null && obj1.Property == obj2.Property ) )
...
//becomes
if( obj1.NullCompare( obj2, (x,y) => x.Property == y.Property )
...
Run Code Online (Sandbox Code Playgroud)
以这种方式使用扩展的缺陷是什么?其他编码员可能会感到困惑吗?这只是滥用扩展吗?
我想我真正想要的是编译器/语言扩展:
propertyValue1 = myObject != null ? myObject.StringProperty : null;
//becomes
propertyValue1 = myObject?StringProperty;
Run Code Online (Sandbox Code Playgroud)
这将使复杂案例变得更加容易:
propertyValue1 = myObject != null ?
(myObject.ObjectProp != null ? myObject.ObjectProp.StringProperty) : null
//becomes
propertyValue1 = myObject?ObjectProp?StringProperty;
Run Code Online (Sandbox Code Playgroud)
这只适用于值类型,但您可以返回可空的等价物:
int? propertyValue2 = myObject?ObjectProp?IntProperty;
//or
int propertyValue3 = myObject?ObjectProp?IntProperty ?? 0;
Run Code Online (Sandbox Code Playgroud)
Bra*_*ger 16
我们独立提出了完全相同的扩展方法名称和实现:Null传播扩展方法.因此,我们认为这不会令人困惑或滥用扩展方法.
我会用链接编写你的"多级"示例,如下所示:
propertyValue1 = myObject.IfNotNull(o => o.ObjectProp).IfNotNull(p => p.StringProperty);
Run Code Online (Sandbox Code Playgroud)
Microsoft Connect上有一个现已关闭的错误提议"?".作为执行此空传播的新C#运算符.Mads Torgersen(来自C#语言团队)简要解释了为什么他们不会实现它.
Mar*_*ade 15
这是针对链式成员的另一种解决方案,包括扩展方法:
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;
}
Run Code Online (Sandbox Code Playgroud)
然后你可以在任何可以为null或none的情况下执行此操作:
foo.PropagateNulls(x => x.ExtensionMethod().Property.Field.Method());
Run Code Online (Sandbox Code Playgroud)
怎么
propertyValue1 = myObject.IfNotNull(o => o.ObjectProp.IfNotNull( p => p.StringProperty ) );
Run Code Online (Sandbox Code Playgroud)
比阅读和写作更容易
if(myObject != null && myObject.ObjectProp != null)
propertyValue1 = myObject.ObjectProp.StringProperty;
Run Code Online (Sandbox Code Playgroud)
Jafar Husain发布了一个使用表达式树来检查链中的null ,C#3中的运行时宏的示例.
这显然有性能影响.现在,如果我们在编译时有办法做到这一点.
我只想说我喜欢这个黑客!
我没有意识到扩展方法并不意味着无效检查,但它完全有意义.正如詹姆斯指出的那样,扩展方法调用本身并不比普通方法贵,但是如果你正在做大量的事情,那么遵循Njor对象模式是有意义的,ljorquera建议.或者使用null对象和?? 一起.
class Class1
{
public static readonly Class1 Empty = new Class1();
.
.
x = (obj1 ?? Class1.Empty).X;
Run Code Online (Sandbox Code Playgroud)