С# элегантный способ проверить, является ли свойство свойства null

В С# скажите, что вы хотите вывести значение свойства PropertyC в этом примере, а ObjectA, PropertyA и PropertyB могут быть пустыми.

ObjectA.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;

Возможно, еще больше рухнуло с помощью оператора с нулевым коалесцированием.

EDIT Первоначально я сказал, что мой второй пример был как js, но я изменил его на psuedo-code, так как он правильно указал, что он не будет работать в js.

Ответ 2

Метод коротких расширений:

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/

EDIT:

После использования этого момента я думаю, что правильное имя для этого метода должно быть IfNotNull() вместо оригинального With().

Ответ 3

Можете ли вы добавить метод в свой класс? Если нет, подумали ли вы об использовании методов расширения? Вы можете создать метод расширения для вашего типа объекта с именем 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 или выше.

Ответ 4

Как вы это делаете, правильно.

Вы можете использовать трюк, подобный описанному здесь, используя выражения Linq:

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

Но это намного медленнее, чем вручную проверять каждое свойство...

Ответ 6

Обновление 2014: у С# 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

Ответ 7

Вы явно ищете 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, если любое из свойств с нулевым значением равно 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);
    }
}

Ответ 8

Этот код является "наименьшим количеством кода", но не лучшей практикой:

try
{
    return ObjectA.PropertyA.PropertyB.PropertyC;
}
catch(NullReferenceException)
{
     return null;
}

Ответ 9

Предполагая, что у вас есть пустые значения типов, один из них будет таким:

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

Я большой поклонник С#, но очень хорошая вещь в новой Java (1.7?) - это. Оператор:

 var x = objectA.?PropertyOfB.?PropertyOfC.?PropertyOfString;

Ответ 10

Отметьте этот пост в блоге. Я думаю, что это очень элегантный метод для цепочки нулевых проверок. Есть много подобных реализаций этого, но мне это нравится, потому что он перестает оценивать, как только в цепочке обнаруживается нуль.

Весь исходный код находится на github.

Ответ 11

Когда мне нужно связать подобные вызовы, я полагаюсь на вспомогательный метод, который я создал, 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);

Ответ 12

Я видел что-то в новом С# 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;

Надеюсь, это помогло кому-то.


UPDATE:

Теперь вы можете сделать это сейчас

 var city = (Person != null)? 
           ((Person.Contact!=null)? 
              ((Person.Contact.Address!= null)?
                      ((Person.Contact.Address.City!=null)? 
                                 Person.Contact.Address.City : null )
                       :null)
               :null)
            : null;

Ответ 13

Вы можете сделать это:

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);

Ответ 14

Это невозможно. ObjectA.PropertyA.PropertyB не сработает, если ObjectA имеет значение null из-за нулевой разыменования, что является ошибкой.

if (ObjectA!= null && ObjectA.PropertyA... работает из-за короткого замыкания, то есть ObjectA.PropertyA никогда не будет проверяться, если ObjectA имеет значение null.

Первый способ, который вы предлагаете, является лучшим и наиболее понятным с намерением. Если что-нибудь, что вы могли бы попытаться перепроектировать, не полагаясь на так много нулей.

Ответ 15

Просто наткнулся на этот пост.

Некоторое время назад я сделал предложение 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;

Ответ 16

вы можете использовать следующее расширение, и я думаю, что это действительно хорошо:

/// <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);

Ответ 17

Нулевое распространение запланировано в С# vNext, работающее на Roslyn


Не ответ, а больше обновлений. Кажется, что UserVoice управляет этим, наряду с множеством других новых вещей, поскольку Roslyn и, по-видимому, является плановым дополнением:

https://roslyn.codeplex.com/discussions/540883

Ответ 18

Я бы написал свой собственный метод в типе PropertyA (или метод расширения, если он не был вашим типом), используя аналогичный шаблон для типа Nullable.

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

   public PropertyBType GetPropertyBOrDefault()
   {
       return PropertyB != null ? PropertyB : defaultValue;
   }
}

Ответ 19

Этот подход довольно прямолинейный, как только вы преодолеете лямбда-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));

Ответ 20

Я написал метод, который принимает значение по умолчанию, вот как его использовать:

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);
    }
}

Ответ 21

var result = nullableproperty ?? defaultvalue;

оператор означает, что если первый аргумент равен нулю, верните вместо него второй.