112

在 C# 中,假设您想在此示例中从 PropertyC 中提取一个值,并且 ObjectA、PropertyA 和 PropertyB 都可以为空。

对象A.PropertyA.PropertyB.PropertyC

如何用最少的代码安全地获得 PropertyC?

现在我会检查:

if(ObjectA != null && ObjectA.PropertyA !=null && ObjectA.PropertyA.PropertyB != null)
{
    // safely pull off the value
    int value = objectA.PropertyA.PropertyB.PropertyC;
}

做更多这样的事情(伪代码)会很好。

int value = ObjectA.PropertyA.PropertyB ? ObjectA.PropertyA.PropertyB : defaultVal;

使用空合并运算符可能会进一步崩溃。

编辑最初我说我的第二个示例就像 js,但我将其更改为伪代码,因为它被正确指出它在 js 中不起作用。

4

19 回答 19

140

在 C# 6 中,您可以使用Null 条件运算符。所以原始测试将是:

int? value = objectA?.PropertyA?.PropertyB?.PropertyC;
于 2014-11-07T02:03:32.820 回答
28

短扩展方法:

public static TResult IfNotNull<TInput, TResult>(this TInput o, Func<TInput, TResult> evaluator)
  where TResult : class where TInput : class
{
  if (o == null) return null;
  return evaluator(o);
}

使用

PropertyC value = ObjectA.IfNotNull(x => x.PropertyA).IfNotNull(x => x.PropertyB).IfNotNull(x => x.PropertyC);

这个简单的扩展方法以及更多您可以在http://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/上找到的方法

编辑:

在使用它片刻之后,我认为这个方法的正确名称应该是IfNotNull()而不是原来的 With()。

于 2014-04-02T10:56:53.167 回答
16

你可以在你的类中添加一个方法吗?如果没有,您是否考虑过使用扩展方法?您可以为您的对象类型创建一个名为GetPropC().

例子:

public static class MyExtensions
{
    public static int GetPropC(this MyObjectType obj, int defaltValue)
    {
        if (obj != null && obj.PropertyA != null & obj.PropertyA.PropertyB != null)
            return obj.PropertyA.PropertyB.PropertyC;
        return defaltValue;
    }
}

用法:

int val = ObjectA.GetPropC(0); // will return PropC value, or 0 (defaltValue)

顺便说一句,这假设您使用的是 .NET 3 或更高版本。

于 2010-08-12T13:50:14.563 回答
12

你这样做的方式是正确的。

可以使用类似于此处描述的技巧,使用 Linq 表达式:

int value = ObjectA.NullSafeEval(x => x.PropertyA.PropertyB.PropertyC, 0);

但是手动检查每个属性要慢得多......

于 2010-08-12T13:48:18.943 回答
11

重构以遵守得墨忒耳定律

于 2010-08-12T13:58:09.333 回答
10

2014 年更新:C# 6 有一个新的运算符,?.称为“安全导航”或“空传播”

parent?.child

阅读http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe-navigation-operator.aspx了解详情

这一直是一个非常受欢迎的请求 https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c-?tracking_code=594c10a522f8e9bc987ee4a5e2c0b38d

于 2014-05-20T15:04:35.207 回答
8

您显然正在寻找Nullable Monad

string result = new A().PropertyB.PropertyC.Value;

变成

string result = from a in new A()
                from b in a.PropertyB
                from c in b.PropertyC
                select c.Value;

如果任何可空属性为空,则返回null;否则, 的值Value

class A { public B PropertyB { get; set; } }
class B { public C PropertyC { get; set; } }
class C { public string Value { get; set; } }

LINQ 扩展方法:

public static class NullableExtensions
{
    public static TResult SelectMany<TOuter, TInner, TResult>(
        this TOuter source,
        Func<TOuter, TInner> innerSelector,
        Func<TOuter, TInner, TResult> resultSelector)
        where TOuter : class
        where TInner : class
        where TResult : class
    {
        if (source == null) return null;
        TInner inner = innerSelector(source);
        if (inner == null) return null;
        return resultSelector(source, inner);
    }
}
于 2010-08-12T14:03:16.640 回答
6

假设您有类型的空值,一种方法是:

var x = (((objectA ?? A.Empty).PropertyOfB ?? B.Empty).PropertyOfC ?? C.Empty).PropertyOfString;

我是 C# 的忠实粉丝,但新 Java(1.7?)中的一个非常好的东西是 .? 操作员:

 var x = objectA.?PropertyOfB.?PropertyOfC.?PropertyOfString;
于 2010-08-12T14:01:01.813 回答
4

这段代码是“最少的代码”,但不是最佳实践:

try
{
    return ObjectA.PropertyA.PropertyB.PropertyC;
}
catch(NullReferenceException)
{
     return null;
}
于 2010-08-12T13:48:35.930 回答
4

当我需要像这样链接调用时,我依赖于我创建的辅助方法 TryGet():

    public static U TryGet<T, U>(this T obj, Func<T, U> func)
    {
        return obj.TryGet(func, default(U));
    }

    public static U TryGet<T, U>(this T obj, Func<T, U> func, U whenNull)
    {
        return obj == null ? whenNull : func(obj);
    }

在你的情况下,你会像这样使用它:

    int value = ObjectA
        .TryGet(p => p.PropertyA)
        .TryGet(p => p.PropertyB)
        .TryGet(p => p.PropertyC, defaultVal);
于 2013-11-08T17:37:27.837 回答
3

我在新的 C# 6.0 中看到了一些东西,这是通过使用 '?' 而不是检查

例如,而不是使用

if (Person != null && Person.Contact!=null && Person.Contact.Address!= null && Person.Contact.Address.City != null)
{ 
  var city = person.contact.address.city;
}

你只需使用

var city = person?.contact?.address?.city;

我希望它对某人有所帮助。


更新:

你现在可以这样做

 var city = (Person != null)? 
           ((Person.Contact!=null)? 
              ((Person.Contact.Address!= null)?
                      ((Person.Contact.Address.City!=null)? 
                                 Person.Contact.Address.City : null )
                       :null)
               :null)
            : null;
于 2015-06-28T10:28:36.067 回答
2

你可以这样做:

class ObjectAType
{
    public int PropertyC
    {
        get
        {
            if (PropertyA == null)
                return 0;
            if (PropertyA.PropertyB == null)
                return 0;
            return PropertyA.PropertyB.PropertyC;
        }
    }
}



if (ObjectA != null)
{
    int value = ObjectA.PropertyC;
    ...
}

或者更好的是:

private static int GetPropertyC(ObjectAType objectA)
{
    if (objectA == null)
        return 0;
    if (objectA.PropertyA == null)
        return 0;
    if (objectA.PropertyA.PropertyB == null)
        return 0;
    return objectA.PropertyA.PropertyB.PropertyC;
}


int value = GetPropertyC(ObjectA);
于 2010-08-12T13:57:12.893 回答
1

我会使用与 Nullable 类型类似的模式以 PropertyA 类型编写您自己的方法(或者如果它不是您的类型,则使用扩展方法)。

class PropertyAType
{
   public PropertyBType PropertyB {get; set; }

   public PropertyBType GetPropertyBOrDefault()
   {
       return PropertyB != null ? PropertyB : defaultValue;
   }
}
于 2010-08-12T13:47:42.527 回答
1

您可以使用以下扩展名,我认为它非常好:

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<TF, TR>(TF t, Func<TF, TR> f)
    where TF : class
{
    return t != null ? f(t) : default(TR);
}

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<T1, T2, TR>(T1 p1, Func<T1, T2> p2, Func<T2, TR> p3)
    where T1 : class
    where T2 : class
{
    return Get(Get(p1, p2), p3);
}

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<T1, T2, T3, TR>(T1 p1, Func<T1, T2> p2, Func<T2, T3> p3, Func<T3, TR> p4)
    where T1 : class
    where T2 : class
    where T3 : class
{
    return Get(Get(Get(p1, p2), p3), p4);
}

它是这样使用的:

int value = Nulify.Get(objectA, x=>x.PropertyA, x=>x.PropertyB, x=>x.PropertyC);
于 2013-12-10T07:49:36.977 回答
0

这不可能。如果由于 null 取消引用而为 null,
ObjectA.PropertyA.PropertyB则将失败,这是一个错误。ObjectA

if(ObjectA != null && ObjectA.PropertyA...由于短路而工作,即ObjectA.PropertyA永远不会检查是否ObjectAnull

你提出的第一种方式是最好的,也是最明确的。如果有的话,您可以尝试重新设计而不必依赖这么多空值。

于 2010-08-12T13:48:20.867 回答
0

一旦你克服了 lambda gobbly-gook,这种方法就相当简单了:

public static TProperty GetPropertyOrDefault<TObject, TProperty>(this TObject model, Func<TObject, TProperty> valueFunc)  
                                                        where TObject : class
    {
        try
        {
            return valueFunc.Invoke(model);
        }
        catch (NullReferenceException nex)
        {
            return default(TProperty);
        }
    }

使用可能看起来像:

ObjectA objectA = null;

Assert.AreEqual(0,objectA.GetPropertyOrDefault(prop=>prop.ObjectB.ObjectB.ObjectC.ID));

Assert.IsNull(objectA.GetPropertyOrDefault(prop => prop.ObjectB));
于 2012-09-21T20:07:17.477 回答
0

刚刚偶然发现这个帖子。

前段时间我在 Visual Studio Connect 上提出了一个关于添加新???运算符的建议。

http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4104392-add-as-an-recursive-null-reference-check-opera

这需要框架团队的一些工作,但不需要改变语言,只需做一些编译器魔法。想法是编译器应该更改此代码(atm 不允许语法)

string product_name = Order.OrderDetails[0].Product.Name ??? "no product defined";

进入这段代码

Func<string> _get_default = () => "no product defined"; 
string product_name = Order == null 
    ? _get_default.Invoke() 
    : Order.OrderDetails[0] == null 
        ? _get_default.Invoke() 
        : Order.OrderDetails[0].Product == null 
            ? _get_default.Invoke() 
            : Order.OrderDetails[0].Product.Name ?? _get_default.Invoke()

对于空检查,这可能看起来像

bool isNull = (Order.OrderDetails[0].Product ??? null) == null;
于 2013-12-09T13:26:58.167 回答
0

我写了一个接受默认值的方法,这里是如何使用它:

var teacher = new Teacher();
return teacher.GetProperty(t => t.Name);
return teacher.GetProperty(t => t.Name, "Default name");

这是代码:

public static class Helper
{
    /// <summary>
    /// Gets a property if the object is not null.
    /// var teacher = new Teacher();
    /// return teacher.GetProperty(t => t.Name);
    /// return teacher.GetProperty(t => t.Name, "Default name");
    /// </summary>
    public static TSecond GetProperty<TFirst, TSecond>(this TFirst item1,
        Func<TFirst, TSecond> getItem2, TSecond defaultValue = default(TSecond))
    {
        if (item1 == null)
        {
            return defaultValue;
        }

        return getItem2(item1);
    }
}
于 2014-10-08T15:02:04.060 回答
-3
var result = nullableproperty ?? defaultvalue;

( ??null-coalescing operator) 表示如果第一个参数是null,则返回第二个参数。

于 2013-05-10T13:29:04.873 回答