64

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

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.

+0

я не вижу, как работает ваш пример JS. вы должны получить ошибку «ожидаемый объект» всякий раз, когда «ObjectA» или «PropertyA» равны нулю. – lincolnk

+0

Спасибо lincolnk, вы правы, поэтому я сменил его на псевдокод. –

ответ

56

В C# 6 вы можете использовать Null Условный Оператор.Таким образом, оригинальный тест будет:

int? value = objectA?.PropertyA?.PropertyB?.PropertyC; 
0

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

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

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

Ну, в этом случае, очевидно, свойствоB никогда не может быть нулевым. – recursive

+0

Хорошая точка, ужасный выбор типов там. –

12

То, как вы это делаете, является правильным.

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

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

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

1

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

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

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

5

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

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

Вероятно, вы должны поймать исключение NullReferenceException, а не все. – recursive

+1

Ew, пожалуйста, не рекламируйте такой код. Вы могли бы поймать что угодно. Для всех, кого мы знаем, PropertyC полностью прекрасен, но PropertyB бросил 'InvalidOperationException' –

+2

Ребята, я полностью согласен с вами. Этот путь далек от того, чтобы быть «изящным», но вопрос был задан «наименьшим количеством кода», это условие выполняется. –

16

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

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

Refactor наблюдать Law of Demeter

+0

Я не рассматриваю графику объекта только на трех уровнях, чтобы не нуждаться в рефакторинге, когда вы только читаете свойства. Я согласен с тем, что OP хотел вызвать метод для объекта, на который ссылается через PropertyC, но не тогда, когда это свойство, для которого требуется только проверка на нуль перед чтением. В этом примере это может быть так же просто, как Customer.Address.Country, где Страна может быть ссылочным типом, например KeyValuePair. Как бы вы реорганизовали это, чтобы предотвратить необходимость проверки null ref? –

+0

Пример OP на самом деле 4 глубины. Мое предложение состоит не в том, чтобы удалить нулевые проверки проверки, а чтобы найти их в объектах, которые, скорее всего, смогут правильно их обрабатывать. Как и большинство «эмпирических правил», есть исключения, но я не уверен, что это одно. Можем ли мы согласиться не согласиться? – rtalbot

+3

Я согласен с @rtalbot (хотя, честно говоря, @Daz Lewis предлагает пример с 4-мя глубинами, поскольку последний элемент - KeyValuePair). Если что-то возится с объектом Customer, то я не вижу, какой бизнес он просматривает через объект Address-heirarchy. Предположим, что вы позже решили, что KeyValuePair не была такой замечательной идеей для собственности в стране. В этом случае код каждого пользователя должен измениться. Это не очень хороший дизайн. –

5

Если у вас есть пустые значения типов один подход был бы это:

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

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

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

Действительно ли это будет в Java 1.7? Он запрошен в C# в течение длительного времени, но я сомневаюсь, что это когда-нибудь произойдет ... –

+0

К сожалению, у меня нет пустых значений. Этот синтаксис Java выглядит милым, хотя! Я собираюсь проголосовать за это, просто потому, что мне нужен этот синтаксис! –

+3

Thomas: Последний раз, когда я проверил http://tech.puredanger.com/java7/, это подразумевало, что Java получит его. Но теперь, когда я перепроверяю, он говорит: «Нулевое безопасное обращение: нет. Итак, я отменяю свое утверждение и заменяю его новым: он был предложен для Java 1.7, но не сделал этого. – FuleSnabel

9

Вы, очевидно, ищешь Nullable Монады:

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

Почему это метод расширения здесь? Он не используется. –

+1

@MladenMihajlovic: метод расширения 'SelectMany' используется синтаксисом' from ... in ... from ... in ... '. – dtb

+0

Ah thanks @dtb - Я этого не знал. Я на самом деле не пуст, пытаясь найти объяснения linq следующим образом. –

4

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

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

0

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

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)); 
-3
var result = nullableproperty ?? defaultvalue; 

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

+2

Этот ответ не решает проблему OP. Как бы вы применили решение с помощью оператора ObjectA.PropertyA.PropertyB, когда все части выражения (ObjectA, PropertyA и PropertyB) могут быть нулевыми? – Artemix

+0

Верно, я думаю, что даже не прочитал вопрос. Во всяком случае, невозможно ничего не делать: P static void Main (string [] args) { a ca = new a(); var default_value = new a() {b = новый объект()}; var value = (ca ?? default_value) .b ?? default_value.b; } класс a { общественный объект b = null; } –

+0

(ObjectA ?? DefaultMockedAtNull) .PropertyA! = Null? ObjectA.PropertyA.PropertyB: null –

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

Я не думаю, что этот код работает. Каков тип defaultVal? var p = new Person(); Assert.AreEqual (p.TryGet (x => x.FirstName) .TryGet (x => x.LastName) .TryGet (x => x.NickName, "foo"), "foo"); – Keith

+0

Пример, который я написал, следует читать как таковой: ObjectA.PropertyA.PropertyB.PropertyC. Кажется, что ваш код пытается загрузить свойство «LastName» из «FirstName», которое не является предполагаемым использованием. Более правильным примером может быть следующее: var postcode = person.TryGet (p => p.Address) .TryGet (p => p.Postcode); Кстати, мой метод tryGet() очень похож на новую функцию в C# 6.0 - оператор с нулевым условием. Его использование будет таким: var postcode = person? .Address? .Postcode; https://msdn.microsoft.com/en-us/magazine/dn802602.aspx – Emanuel

1

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

Некоторое время назад я сделал предложение по Visual Studio Connect о добавлении нового оператора ???.

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

Это потребует некоторой работы с рамочной команды, но не нужно, чтобы изменить язык, а просто сделать некоторые компилятор магии. Идея заключалась в том, что компилятор должен изменить этот код (синтаксис не разрешено атм)

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

Короткий метод расширения:

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().

1

Null распространение планируется в C# vNext, питание от Рослин


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

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

+0

Удивительные новости! Спасибо, что поделился. –

9

Update 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

+0

Желание я мог бы удвоить upvote для лучшей ручки на этом сайте – pimbrouwers

-1

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

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

Это решение уже было предоставлено в других ответах (неоднократно). Нет никакой причины публиковать его снова *. – Servy

+0

Я не видел никакого accepst значения по умолчанию. –

+0

Я считаю 6 других, которые используют определенное значение по умолчанию. Видимо, ты не выглядел так тяжело. – Servy

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; 

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


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;