Null Coalescing 연산자를 사용하는 고유 한 방법


164

C #에서 Null 통합 연산자를 사용하는 표준 방법은 기본값을 설정하는 것입니다.

string nobody = null;
string somebody = "Bob Saget";
string anybody = "";

anybody = nobody   ?? "Mr. T"; // returns Mr. T
anybody = somebody ?? "Mr. T"; // returns "Bob Saget"

그러나 다른 무엇을 ??사용할 수 있습니까? 다음보다 더 간결하고 읽기 쉬운 것 외에는 삼항 연산자만큼 유용하지 않습니다.

nobody = null;
anybody = nobody == null ? "Bob Saget" : nobody; // returns Bob Saget

따라서 null 병합 연산자에 대한 지식조차도 적습니다 ...

  • ??다른 용도로 사용해 보셨습니까 ?

  • ??필요, 또는 당신은 (가장 잘 알고있는 것으로) 삼항 연산자를 사용한다

답변:


216

우선, 표준 삼항보다 체인을 연결하는 것이 훨씬 쉽습니다.

string anybody = parm1 ?? localDefault ?? globalDefault;

vs.

string anyboby = (parm1 != null) ? parm1 
               : ((localDefault != null) ? localDefault 
               : globalDefault);

null 가능 객체가 변수가 아닌 경우에도 잘 작동합니다.

string anybody = Parameters["Name"] 
              ?? Settings["Name"] 
              ?? GlobalSetting["Name"];

vs.

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];

12
체인 연결은 운영자에게 큰 플러스 중복 IF를의 무리 제거
chakrit

1
방금 삼항 또는 null 병합 연산자에 대해 알기 전에 작성한 간단한 IF 블록을 대체하기 위해 오늘 사용했습니다. 원래 IF 문의 true 및 false 분기는 동일한 메소드를 호출하여 특정 입력이 NULL 인 경우 인수 중 하나를 다른 값으로 대체합니다. null 통합 연산자를 사용하면 한 번의 호출입니다. 이것은 두 개 이상의 그러한 대체가 필요한 방법이있을 때 정말 강력합니다!
David A. Grey

177

나는 그것을 게으른로드 원 라이너로 사용했습니다 :

public MyClass LazyProp
{
    get { return lazyField ?? (lazyField = new MyClass()); }
}

읽을 수 있습니까? 스스로 결정하십시오.


6
흠, 당신은 "누군가 그것을 난독 화 된 IF로 사용하고 싶은가"에 대한 반례를 발견했습니다.
Godeke

6
뭔가 빠졌을 수도 있지만 (주로 Java를 사용합니다) 경쟁 조건이 없습니까?
Justin K

9
@Justin K-여러 스레드가 동일한 객체의 LazyProp 속성에 액세스하는 경우 경쟁 조건 만 있습니다. 각 인스턴스의 스레드 안전성이 필요한 경우 잠금으로 쉽게 고칠 수 있습니다. 이 예에서는 분명히 필요하지 않습니다.
Jeffrey L Whitledge

5
@ Jeffrey : 분명하다면 질문을하지 않았을 것입니다. :)이 예제를 보았을 때 나는 즉시 싱글 톤 멤버를 생각했고 멀티 스레드 환경에서 대부분의 코딩을 수행했기 때문에 코드가 정확하다고 가정하면 여분의 것은 불필요합니다.
저스틴 케이

7
경쟁 조건을 갖기 위해 싱글 톤일 필요는 없습니다. LazyProp가 포함 된 클래스의 공유 인스턴스와 LazyProp에 액세스하는 여러 스레드입니다. Lazy <T>는 이런 종류의 작업을 수행하는 더 좋은 방법이며 기본적으로 스레드로부터 안전합니다 (Lazy <T>의 스레드 안전을 수정하도록 선택할 수 있음).
Niall Connaughton

52

두 가지 "약간 이상한"방법으로 유용하다는 것을 알았습니다.

  • 루틴을 out작성할 때 매개 변수 를 갖는 대안으로 TryParse(예 : 구문 분석에 실패하면 널값을 리턴)
  • 비교를위한 "모름"표현

후자는 조금 더 많은 정보가 필요합니다. 일반적으로 여러 요소가 포함 된 비교를 만들 때는 비교의 첫 번째 부분 (예 : 나이)이 확실한 답을 제공하는지 확인한 다음 첫 번째 부분이 도움이되지 않는 경우에만 다음 부분 (예 : 이름)을 확인해야합니다. null 병합 연산자를 사용하면 순서 또는 평등에 관계없이 매우 간단한 비교를 작성할 수 있습니다. 예를 들어 MiscUtil 에서 두 개의 도우미 클래스 사용 :

public int Compare(Person p1, Person p2)
{
    return PartialComparer.Compare(p1.Age, p2.Age)
        ?? PartialComparer.Compare(p1.Name, p2.Name)
        ?? PartialComparer.Compare(p1.Salary, p2.Salary)
        ?? 0;
}

분명히 MiscUtil에는 ProjectionComparer가 있으며 확장 기능과 함께 이러한 종류의 작업을 훨씬 쉽게 만들 수는 있지만 여전히 깔끔합니다.

Equals를 구현할 때 참조 동등성 (또는 nullity)을 검사하기 위해 동일한 작업을 수행 할 수 있습니다.


PartialComparer로 수행 한 작업이 마음에 들지만 평가 된 식 변수를 유지해야하는 경우를 찾고있었습니다. 나는 람다와 확장에 정통하지 않으므로 다음이 비슷한 패턴을 따르는 지 알 수 있습니까 (예 : 작동합니까)? stackoverflow.com/questions/1234263/#1241780
maxwellb

33

또 다른 장점은 삼항 연산자에 이중 평가 또는 임시 변수가 필요하다는 것입니다.

예를 들어 다음을 고려하십시오.

string result = MyMethod() ?? "default value";

삼항 연산자를 사용하면 다음 중 하나가 남습니다.

string result = (MyMethod () != null ? MyMethod () : "default value");

MyMethod를 두 번 호출하거나

string methodResult = MyMethod ();
string result = (methodResult != null ? methodResult : "default value");

어느 쪽이든, null 병합 연산자는 더 깨끗하고 더 효율적이라고 생각합니다.


1
+1. 이것이 null 통합 연산자를 좋아하는 큰 이유 중 하나입니다. 호출 MyMethod()에 부작용이 있을 때 특히 유용합니다 .
CVn

경우 MyMethod()외부 값을 반환의 영향이없는, 컴파일러는하지 알고 당신이 정말로 대부분의 경우 여기 효율성에 대해 걱정할 필요가 없습니다, 그래서 두 번 호출 할 수 있습니다.
TinyTimZamboni

또한 MyMethod()점선으로 된 일련의 점선 객체 일 때 일을 더 잘 읽을 수 있습니다. 예 :myObject.getThing().getSecondThing().getThirdThing()
xdhmoore

@TinyTimZamboni, 컴파일러 의이 동작에 대한 참조가 있습니까?
Kuba Wyrostek

@ KubaWyrostek C # 컴파일러의 특정 작업에 대한 지식은 없지만 llvm에 대한 정적 컴파일러 이론에 대한 경험이 있습니다. 컴파일러가 이와 같은 호출을 최적화하기 위해 취할 수있는 여러 가지 방법이 있습니다. Global Value Numbering 은 Pure 함수 MyMethod라고 가정 할 때이 문맥에서 두 호출 이 동일하다는 것을 알 수 MyMethod있습니다. 또 다른 옵션은 자동 메모 또는 기능을 캐시에서 닫는 것입니다. 반면에 : en.wikipedia.org/wiki/Global_value_numbering
TinyTimZamboni

23

고려해야 할 또 다른 사항은 통합 연산자는 삼항처럼 속성의 get 메소드를 두 번 호출하지 않는다는 것입니다.

따라서 삼항을 사용해서는 안되는 시나리오가 있습니다.

public class A
{
    var count = 0;
    private int? _prop = null;
    public int? Prop
    {
        get 
        {
            ++count;
            return _prop
        }
        set
        {
            _prop = value;
        }
    }
}

사용하는 경우 :

var a = new A();
var b = a.Prop == null ? 0 : a.Prop;

getter가 두 번 호출되고 count변수는 2와 같습니다.

var b = a.Prop ?? 0

count예상대로 변수는 1에 해당 될 것입니다.


4
이로 인해 더 많은 투표가 필요합니다. 나는 sooo를 여러 번 읽은 ??이다 상당 에를 ?:.
Kuba Wyrostek

1
게터가 두 번 호출되는 것에 대한 유효 지점. 그러나이 예제에서는 잘못된 디자인 패턴이 객체를 실제로 변경하기 위해 잘못된 이름을 가진 getter를 갖는 것으로 간주합니다.
Linas 2016 년

15

??연산자 에게 가장 큰 장점은 nullable 값 형식을 nullable이 아닌 형식으로 쉽게 변환 할 수 있다는 것입니다.

int? test = null;
var result = test ?? 0; // result is int, not int?

Linq 쿼리에서 이것을 자주 사용합니다.

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?

나는 약간 늦을 수도 있지만, 두 번째 예는 if을 던질 것 kvp == null입니다. 그리고 실제로 내가 일반적으로 사용 Nullable<T>하는 GetValueOrDefault방법이 있습니다.
CompuChip

6
KeyValuePair는 .NET 프레임 워크의 값 유형이므로 해당 속성에 액세스하면 null 참조 예외가 발생하지 않습니다. msdn.microsoft.com/ko-kr/library/5tbh8a42(v=vs.110).aspx
Ryan

9

내가 사용 했어 ?? 내 IDataErrorInfo 구현에서 :

public string Error
{
    get
    {
        return this["Name"] ?? this["Address"] ?? this["Phone"];
    }
}

public string this[string columnName]
{
    get { ... }
}

개별 속성이 "오류"상태 인 경우 해당 오류가 발생하고 그렇지 않으면 null이 발생합니다. 정말 잘 작동합니다.


흥미 롭군 "this"를 속성으로 사용하고 있습니다. 나는 그런 짓을 한 적이 없다.
Armstrongest

예, IDataErrorInfo 작동 방식의 일부입니다. 일반적으로이 구문은 컬렉션 클래스에서만 유용합니다.
Matt Hamilton

4
당신은에서 오류 메시지를 저장하고 this["Name"], this["Address"]등?
앤드류

7

선택적 매개 변수가 설정되지 않은 경우를 처리하기 위해 null 병합 연산자를 사용하여 조금 더 깔끔하게 만들 수 있습니다.

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...

이 줄을 다음과 같이 쓸 수 있다면 좋지 않을까요 arg ?= Arg.Default?
Jesse de Wit

6

null 병합 연산자를 사용하여 특정 속성을 지연로드하고 싶습니다.

내 요점을 설명하기 위해 매우 간단하고 고안된 예 :

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}

Resharper는 실제로 이것을 "전통적인"게으른 하중에 대한 리 팩터로 제안합니다.
arichards

5

?? 필요하거나 삼항 연산자를 사용해야합니까 (대부분 익숙한)

실제로, 내 경험에 따르면 너무 적은 수의 사람들이 삼항 연산자 (또는 더 정확하게는 조건부 연산자; 이진 또는 이진 또는 이진 ?:과 같은 의미에서 "삼항")에 익숙 합니다. 여러 언어로 된 삼항 연산자 만) 적어도 제한된 샘플에서는 진술이 실패합니다.||+

또한 앞에서 언급했듯이 null 병합 연산자가 매우 유용하고 평가할 식에 부작용이 생길 때 중요한 상황이 하나 있습니다. 이 경우에는 할 수 없습니다 (a) 임시 변수를 도입하거나 (b) 응용 프로그램의 실제 논리를 변경하지 않으면 조건부 연산자를 사용할 . (b) 어떤 상황에서도 분명히 적절하지 않으며 개인적인 취향이지만, 단기 변수가 있더라도 외부 적으로 많은 선언문을 어지럽히는 것을 좋아하지 않으므로 (a)도 마찬가지입니다. 특정 시나리오.

물론 결과에 대해 여러 번 점검해야하는 경우 조건부 연산자 또는 if블록 세트가 작업의 도구 일 수 있습니다. 그러나 간단한 "이것이 null이면 그것을 사용하고 그렇지 않으면 그것을 사용하십시오", null 병합 연산자 ??는 완벽합니다.


나로부터 매우 늦은 의견-그러나 삼항 연산자는 세 개의 인수 (현재 C #에 둘 이상 있음)가있는 연산자라는 것을 누군가가 알게되어 기뻤습니다.
Steve Kidd

5

최근에 많이 해왔 던 한 가지는 백업에 null 병합을 사용하는 것 as입니다. 예를 들면 다음과 같습니다.

object boxed = 4;
int i = (boxed as int?) ?? 99;

Console.WriteLine(i); // Prints 4

또한 ?.각각 실패 할 수있는 긴 체인을 백업하는 데 유용합니다.

int result = MyObj?.Prop?.Foo?.Val ?? 4;
string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ?? "not there";

4

유일한 문제는 null-coalesce 연산자가 빈 문자열을 감지하지 못한다는 것입니다.


string result1 = string.empty ?? "dead code!";

string result2 = null ?? "coalesced!";

산출:

result1 = ""

result2 = coalesced!

나는 현재 재정의를 찾고 있습니까? 이 문제를 해결하려면 연산자를 사용하십시오. 이것을 프레임 워크에 내장하는 것이 편리 할 것입니다.

생각?


Extension 메서드를 사용 하여이 작업을 수행 할 수 있지만 코드에 추가되어 웹 컨텍스트에서 매우 유용하다는 데 동의합니다.
Armstrongest

네, 이것은 빈번한 시나리오입니다. 특별한 방법이 있습니다. String.IsNullOrEmpty (string) ...
Max Galkin

12
"널 병합 연산자는 빈 문자열을 감지하지 못합니다." 이것은 nullOrEmpty-coalescing 연산자 가 아니라 null - coalescing 연산자입니다. 그리고 개인적으로, 두 언어를 구별하는 언어로 null 값과 빈 값을 혼합하는 것을 멸시합니다. 이는 성 가시지 않은 것들과 인터페이스합니다. 그리고 나는 약간 강박 관념이므로 언어 ​​/ 구현이 왜 두 가지를 구별하지 못하더라도 왜 그런지 이유를 이해하더라도 ([대부분의 구현?] SQL과 같은) 나를 짜증나게합니다.
JAB

3
??msdn.microsoft.com/en-us/library/8edha89s(v=vs.100).aspx는 과부하가되지 않는 것이 좋습니다. : 나는 조합 사용 반환 문자열이 비어있을 때의 경우입니다. s1.Nullify() ?? s2.Nullify()string Nullify(this s)null
키트

유일한 문제? 방금 ?? =를 원한다는 것을 알았고 그것을 할 수있는 방법이 있는지 보면서이 스레드를 찾았습니다. (상황 : 첫 번째 패스에서 예외 사례가로드되었으므로 이제 돌아가서 아직로드되지 않은 항목으로 기본값을로드하려고합니다.)
Loren Pechtel

3

?? 필요하거나 삼항 연산자를 사용해야합니까 (대부분 익숙한)

자신의 의도를 가장 잘 나타내는 것을 사용해야합니다. 이 때문에 널 병합 연산자가 사용 .

반면에, 그것은 매우 전문적이기 때문에 다른 용도가 있다고 생각하지 않습니다. ||다른 언어와 마찬가지로 연산자 의 적절한 과부하를 선호했을 것입니다. 이것은 언어 디자인에서 더 포용 적입니다. 그러나 잘…


3

멋있는! 널 병합 연산자에 대해 알지 못하는 사람으로 나를 계산하십시오. 그것은 꽤 멋진 일입니다.

삼항 연산자보다 읽기가 훨씬 쉽습니다.

내가 사용할 수있는 첫 번째 장소는 모든 기본 매개 변수를 한곳에 유지하는 것입니다.

public void someMethod( object parm2, ArrayList parm3 )
{ 
  someMethod( null, parm2, parm3 );
}
public void someMethod( string parm1, ArrayList parm3 )
{
  someMethod( parm1, null, parm3 );
}
public void someMethod( string parm1, object parm2, )
{
  someMethod( parm1, parm2, null );
}
public void someMethod( string parm1 )
{
  someMethod( parm1, null, null );
}
public void someMethod( object parm2 )
{
  someMethod( null, parm2, null );
}
public void someMethod( ArrayList parm3 )
{
  someMethod( null, null, parm3 );
}
public void someMethod( string parm1, object parm2, ArrayList parm3 )
{
  // Set your default parameters here rather than scattered through the above function overloads
  parm1 = parm1 ?? "Default User Name";
  parm2 = parm2 ?? GetCurrentUserObj();
  parm3 = parm3 ?? DefaultCustomerList;

  // Do the rest of the stuff here
}

2

이상한 유스 케이스이지만 IDisposable객체가 arg로 전달 될 수 있는 메소드가 있었 으므로 부모에 의해 처리 될 수도 있지만 null 일 수도 있습니다 (따라서 로컬 메소드에서 작성 및 처리해야 함)

그것을 사용하기 위해 코드는

Channel channel;
Authentication authentication;

if (entities == null)
{
    using (entities = Entities.GetEntities())
    {
        channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
        [...]
    }
}
else
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

그러나 유휴 합병으로 훨씬 더 깔끔해집니다.

using (entities ?? Entities.GetEntities())
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

0

나는 이렇게 사용했다 :

for (int i = 0; i < result.Count; i++)
            {
                object[] atom = result[i];

                atom[3] = atom[3] ?? 0;
                atom[4] = atom[4] != null ? "Test" : string.Empty;
                atom[5] = atom[5] ?? "";
                atom[6] = atom[6] ?? "";
                atom[7] = atom[7] ?? "";
                atom[8] = atom[8] ?? "";
                atom[9] = atom[9] ?? "";
                atom[10] = atom[10] ?? "";
                atom[12] = atom[12] ?? false; 
            }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.