속성의 속성이 null인지 확인하는 C # 우아한 방법


98

C #에서는이 예제에서 PropertyC의 값을 가져 오려고하며 ObjectA, PropertyA 및 PropertyB는 모두 null 일 수 있습니다.

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;

null-coalescing 연산자로 훨씬 더 축소되었을 수 있습니다.

편집 원래 두 번째 예제는 js와 비슷하다고 말했지만 js에서 작동하지 않는다고 올바르게 지적 되었기 때문에 가짜 코드로 변경했습니다.


귀하의 js 예제가 어떻게 작동하는지 모르겠습니다. 당신은 "개체 예상"오류가 발생 할 때마다해야 ObjectA또는 PropertyAnull입니다.
lincolnk 2010 년

답변:


120

C # 6에서는 Null 조건부 연산자를 사용할 수 있습니다 . 따라서 원래 테스트는 다음과 같습니다.

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

2
이것이 무엇을하는지 설명해 주시겠습니까? valueif PropertyCis null 은 무엇입니까 ? 또는 PropertyBnull 인 경우 ? 만약에 Object A널 (null)은?
Kolob Canyon

1
이러한 속성 중 하나라도 null이면 전체 문이 null. 왼쪽에서 오른쪽으로 시작합니다. 문법을 설탕없는이 시리즈에 해당 if 문 경우 if(propertyX == null) {value = null} else if (propertyY == null){ value = null} else if......최종 마지막 표현의 존재와if(propertyZ != null) { value = propertyZ }
DetectivePikachu

27

짧은 확장 방법 :

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/에서 찾을 수 있습니다.

편집하다:

잠시 사용한 후에이 메서드의 적절한 이름 은 원래 With () 대신 IfNotNull () 이어야한다고 생각합니다 .


15

수업에 메소드를 추가 할 수 있습니까? 그렇지 않다면 확장 방법 사용에 대해 생각해 보셨습니까? 라는 개체 유형에 대한 확장 메서드를 만들 수 있습니다 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 이상을 사용하고 있다고 가정합니다.


11

당신이하는 방식은 정확합니다.

당신은 할 수 설명 된 것과 같은 트릭을 사용 여기 의 LINQ 표현을 사용하여 :

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

그러나 각 속성을 수동으로 확인하는 것이 훨씬 느립니다.


10

데메테르법칙 을 준수하기위한 리팩터링


속성을 읽을 때만 리팩토링이 필요한 개체 그래프는 3 단계 깊이 만 고려하지 않습니다. OP가 PropertyC를 통해 참조되는 개체에 대한 메서드를 호출하고 싶었지만 읽기 전에 null 만 확인해야하는 속성 인 경우에는 동의하지 않습니다. 이 예에서는 Customer.Address.Country처럼 간단 할 수 있습니다. 여기서 Country는 KeyValuePair와 같은 참조 유형일 수 있습니다. null ref 검사가 필요하지 않도록 어떻게 리팩터링 하시겠습니까?
Darren Lewis

OP 예제는 실제로 4 깊이입니다. 내 제안은 null ref 검사를 제거하는 것이 아니라 적절하게 처리 할 수있을 가능성이 가장 높은 개체에서 찾는 것입니다. 대부분의 "경험의 법칙"과 마찬가지로 예외가 있지만 이것이 하나라고 확신하지는 않습니다. 동의하지 않는 데 동의 할 수 있습니까?
rtalbot 2010-08-12

4
나는 @rtalbot에 동의합니다 (공정하게 @Daz Lewis는 마지막 항목이 KeyValuePair이기 때문에 4 가지 깊이의 예를 제안합니다). 고객 개체에 문제가있는 경우 주소 개체 상속을 통해 어떤 비즈니스를보고 있는지 알 수 없습니다. 나중에 KeyValuePair가 Country 속성에 대해 그다지 좋은 아이디어가 아니라고 결정했다고 가정합니다. 이 경우 모든 사람의 코드를 변경해야합니다. 좋은 디자인이 아닙니다.
Jeffrey L Whitledge

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


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;

nullnullable 속성이 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);
    }
}

여기에 확장 방법이있는 이유는 무엇입니까? 사용되지 않습니다.
Mladen Mihajlovic 2014

1
@MladenMihajlovic : SelectMany확장 메서드가 from ... in ... from ... in ...구문에 사용됩니다 .
dtb

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 #에서 요청되었지만, 앞으로도 일어날 것
같지는 않습니다

불행히도 빈 값이 없습니다. 그래도 Java 구문은 멋져 보입니다! 나는 그 구문을 원하기 때문에 이것을 찬성 할 것입니다!
Jon Kragh

3
Thomas : 제가 지난번tech.puredanger.com/java7 을 확인 했을 때 Java가 얻을 수 있다는 것을 암시했습니다. 그러나 이제 다시 확인하면 다음과 같이 표시됩니다. Null safe handling : NO. 그래서 저는 제 진술을 취소하고 새로운 진술로 바꿉니다. Java 1.7 용으로 제안되었지만 만들지 않았습니다.
또 다른 메타

추가 방법은 monad.net에 의해 사용되는 하나입니다
그냥 다른 metaprogrammer

1
처럼 보인다?. 연산자는 Visual Studio 2015에 있습니다 https://msdn.microsoft.com/en-us/library/dn986595.aspx
Edward

4

이 코드는 "최소한의 코드"이지만 모범 사례는 아닙니다.

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

1
나는 이와 같은 코드를 많이 보았고 성능 손실을 무시하고 가장 큰 문제는 실제 예외가 수백만 개의 쓸모없는 null ref 예외로 익사하기 때문에 디버깅이 복잡하다는 것입니다.
또 다른 메타

3 년이 지난 후 제 답을 읽는 것이 가끔 재미 있습니다. 오늘은 다르게 대답 할 것 같아요. 나는 코드가 데메테르의 법칙을 위반한다고 말하고 싶습니다.
Boris Modylevsky 2010 년

1
오늘부터 원래 답변 7 년 후 @Phillip Ngan에 가입하고 다음 구문으로 C # 6을 사용합니다. int? value = objectA? .PropertyA? .PropertyB? .PropertyC;
보리스 Modylevsky

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

이 코드가 작동하지 않는다고 생각합니다. defaultVal의 유형은 무엇입니까? var p = new Person (); Assert.AreEqual (p.TryGet (x => x.FirstName) .TryGet (x => x.LastName) .TryGet (x => x.NickName, "foo"), "foo");
Keith

내가 작성한 예제는 ObjectA.PropertyA.PropertyB.PropertyC와 같이 읽어야합니다. 귀하의 코드는 의도 된 용도가 아닌 "FirstName"에서 "LastName"이라는 속성을로드하려고하는 것 같습니다. 더 정확한 예는 다음과 같습니다. var postcode = person.TryGet (p => p.Address) .TryGet (p => p.Postcode); 그건 그렇고, 내 TryGet () 도우미 메서드는 C # 6.0의 새로운 기능인 null 조건부 연산자와 매우 유사합니다. 사용법은 다음과 같습니다. var postcode = person? .Address? .Postcode; msdn.microsoft.com/en-us/magazine/dn802602.aspx
Emanuel

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;

1

다음과 같이 할 수 있습니다.

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

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

0

Nullable 형식과 유사한 패턴을 사용하여 PropertyA 형식 (또는 형식이 아닌 경우 확장 메서드)에 고유 한 메서드를 작성합니다.

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

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

이 경우 PropertyB는 절대 null이 될 수 없습니다.
재귀

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

null 검사의 경우 다음과 같이 보일 수 있습니다.

bool isNull = (Order.OrderDetails[0].Product ??? null) == null;

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

1
이 솔루션은 이미 다른 답변에서 (반복적으로) 제공되었습니다. 다시 게시 할 이유가 전혀 없습니다 .
Servy

나는 기본값을 수용하는 것을 보지 못했습니다.
Akira Yamamoto

정의 된 기본값을 사용하는 다른 6 명을 계산합니다. 분명히 당신은 그렇게 힘들게 보이지 않았습니다.
Servy

0

불가능합니다. null 역 참조로 인해이 null 인
ObjectA.PropertyA.PropertyB경우 실패 ObjectA하며 이는 오류입니다.

if(ObjectA != null && ObjectA.PropertyA...에 의한 단락에 작품, 즉 ObjectA.PropertyA경우 확인되지 않을 것 ObjectA입니다 null.

당신이 제안하는 첫 번째 방법은 의도적으로 가장 명확하고 가장 명확합니다. 너무 많은 null에 의존하지 않고도 재 설계를 시도 할 수 있다면.


-1

이 접근 방식은 람다 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));

내가 응답을 제공 한 후 8 년 동안 누군가가 왜 반대표를 던 졌는지 궁금합니다 (C # 6의 null 병합이 문제가되기 몇 년 전이었습니다).
BlackjacketMack

-3
var result = nullableproperty ?? defaultvalue;

??첫 번째 인자 인 경우 (널 유착 연산자) 수단 null대신 초를 반환한다.


2
이 답변은 OP의 문제를 해결하지 않습니다. 솔루션을 어떻게 적용 하시겠습니까 ?? 표현식의 모든 부분 (ObjectA, PropertyA 및 PropertyB)이 null 일 수있는 경우 연산자를 ObjectA.PropertyA.PropertyB로
Artemix 2013 년

사실, 나는 질문을 전혀 읽지 않은 것 같습니다. 어쨌든, 불가능은 아무것도하지 마십시오. : P static void Main (string [] args) {a ca = new a (); var default_value = new a () {b = new object ()}; var value = (ca ?? default_value) .b ?? default_value.b; } 클래스 a {공용 객체 b = null; }
Aridane

(ObjectA ?? DefaultMockedAtNull) .PropertyA = null이 ObjectA.PropertyA.PropertyB!? 널
Aridane 알라모
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.