C # 8 널 입력 불가능 참조 및 Try 패턴


22

예로 C # 1 클래스에 패턴 존재 Dictionary.TryGetValue하고 int.TryParse: 방법은 복귀 동작과 실제 결과를 포함하는 출력 파라미터의 성공을 나타내는 부울; 작업이 실패하면 out 매개 변수가 null로 설정됩니다.

내가 nullable이 아닌 C # 8 참조를 사용하고 있고 내 클래스에 대해 TryParse 메서드를 작성하려고한다고 가정 해 봅시다. 올바른 서명은 이것이다 :

public static bool TryParse(string s, out MyClass? result);

거짓 인 경우 결과가 널이므로 출력 변수를 널 입력 가능으로 표시해야합니다.

그러나 Try 패턴은 일반적으로 다음과 같이 사용됩니다.

if (MyClass.TryParse(s, out var result))
{
  // use result here
}

작업이 성공할 때 지점에만 입력하므로 해당 지점에서 결과가 null이되어서는 안됩니다. 그러나 그것을 nullable로 표시했기 때문에 이제 그것을 확인하거나 !재정의 하는 데 사용해야 합니다.

if (MyClass.TryParse(s, out var result))
{
  Console.WriteLine("Look: {0}", result.SomeProperty); // compiler warning, could be null
  Console.WriteLine("Look: {0}", result!.SomeProperty); // need override
}

이것은 추악하고 약간 비인간적입니다.

일반적인 사용 패턴으로 인해 다른 옵션이 있습니다 : 결과 유형에 대한 거짓말 :

public static bool TryParse(string s, out MyClass result) // not nullable
{
   // Happy path sets result to non-null and returns true.
   // Error path does this:
   result = null!; // override compiler complaint
   return false;
}

이제 일반적인 사용법이 더 좋아집니다.

if (MyClass.TryParse(s, out var result))
{
  Console.WriteLine("Look: {0}", result.SomeProperty); // no warning
}

그러나 비정형적인 사용법에는 다음과 같은 경고가 표시되지 않습니다.

else
{
  Console.WriteLine("Fail: {0}", result.SomeProperty);
  // Yes, result is in scope here. No, it will never be non-null.
  // Yes, it will throw. No, the compiler won't warn about it.
}

이제 어느 길로 갈지 잘 모르겠습니다. C # 언어 팀의 공식 추천이 있습니까? 이 작업을 수행하는 방법을 보여줄 수있는 CoreFX 코드가 null이 아닌 참조로 이미 변환되어 있습니까? ( TryParse메서드를 찾았습니다 . IPAddress클래스가 하나 있지만 corefx의 마스터 분기에서 변환되지 않았습니다.)

그리고 일반적인 코드는 어떻게 Dictionary.TryGetValue이것을 처리합니까? (아마도 MaybeNull내가 찾은 것의 특수 속성으로 가능합니다 .) Dictionarynullable이 아닌 값 형식으로 인스턴스화하면 어떻게됩니까 ?


시도하지 않았기 때문에 (이것이 대답으로 작성되지 않는 이유), switch 문의 새로운 패턴 일치 기능을 사용하면 nullable 참조를 반환하는 옵션이 하나 있습니다. return MyClass?) 및 a case MyClass myObj:및 (선택 사항) 으로 스위치를 켭니다 case null:.
Filip Milovanović

+1 나는이 질문을 정말로 좋아하고,이 작업을 할 때 항상 무시 대신 여분의 null 검사를 사용했습니다. 항상 약간 불필요하고 우아하다고 느꼈지만 성능에 중요한 코드는 없었습니다. 그냥 놓아 버렸어요 더 깔끔한 방법으로 처리 할 수 ​​있다면 좋을 것입니다!
BrianH

답변:


10

bool / out-var 패턴은 설명 할 때 nullable 참조 유형에서 제대로 작동하지 않습니다. 따라서 컴파일러와 싸우지 말고 기능을 사용하여 단순화하십시오. C # 8의 향상된 패턴 일치 기능을 사용하면 nullable 참조를 "가난한 사람의 유형"으로 취급 할 수 있습니다.

public static MyClass? TryParse(string s) => 



if (TryParse(someString) is {} myClass)
{
    // myClass wasn't null, we are good to use it
}

그렇게하면 out매개 변수로 혼란을 피할 수 있으며 nullnullable이 아닌 참조와 혼합 하는 것 보다 컴파일러와 싸울 필요가 없습니다 .

그리고 일반적인 코드는 어떻게 Dictionary.TryGetValue이것을 처리합니까?

이 시점에서 그 "가난한 사람의 유형"은 무너집니다. NRT (Nullable Reference Type)를 사용할 때 컴파일러는 Foo<T>Null을 허용하지 않는 것으로 간주한다는 문제가 발생합니다 . 그러나 시도로 변경 Foo<T?>그리고 그 할 것 T널 (NULL) 값 형식으로 클래스 또는 구조체에 제약이보기의 CLR의 관점에서 매우 다른 것입니다. 이에 대한 다양한 해결 방법이 있습니다.

  1. NRT 기능을 사용하지 마십시오.
  2. 코드에 null이없는 경우에도 매개 변수에 default(와 함께 !)를 사용하십시오 out.
  3. 실제 사용 Maybe<T>후 결코 반환 값으로 유형 null및 래핑 boolout THasValueValue속성이나 일부 등,
  4. 튜플을 사용하십시오.
public static (bool success, T result) TryParse<T>(string s) => 


if (TryParse<MyClass>(someString) is (true, var result))
{
    // result is valid here, as success is true
}

개인적으로 사용하는 것을 선호 Maybe<T>하지만 해체를 지원하므로 위의 4에서와 같이 튜플처럼 패턴 일치 할 수 있습니다.


2
TryParse(someString) is {} myClass-이 문법은 익숙해 지겠지만 아이디어가 마음에 듭니다.
Sebastian Redl

TryParse(someString) is var myClass나에게 더 쉬워 보인다.
Olivier Jacot-Descombes가

2
@ OlivierJacot-Descombes it 더 쉬워 보일 수 있지만 ... 작동하지 않습니다. 자동차 패턴은 항상 일치하므로 null x is var y여부에 관계없이 항상 true x입니다.
David Arno

15

나처럼이 늦게 도착하면 .NET 팀 은 try 패턴에 사용할 수 MaybeNullWhen(returnValue: true)있는 System.Diagnostics.CodeAnalysis공간 과 같은 많은 매개 변수 속성을 통해 해결했습니다 .

예를 들면 다음과 같습니다.

Dictionary.TryGetValue와 같은 일반 코드는 어떻게 처리합니까?

bool TryGetValue(TKey key, [MaybeNullWhen(returnValue: false)] out TValue value);

확인하지 않으면 소리를 지르는 것을 의미합니다. true

// This is okay:
if(myDictionary.TryGetValue("cheese", out var result))
{
  var more = result * 42;
}

// But this is not:
_ = myDictionary.TryGetValue("cheese", out var result);
var more = result * 42;
// "CS8602: Dereference of a potentially null reference"

자세한 내용은 :


3

나는 여기에 갈등이 없다고 생각합니다.

당신의 반대

public static bool TryParse(string s, out MyClass? result);

이다

작업이 성공할 때 지점에만 입력하므로 해당 지점에서 결과는 null이되어서는 안됩니다.

그러나 실제로 이전 스타일 TryParse 함수에서 out 매개 변수에 null을 할당하는 것을 막을 수있는 것은 없습니다.

예.

MyJsonObject.TryParse("null", out obj) //sets obj to a null MyJsonObject and returns true

확인하지 않고 out 매개 변수를 사용할 때 프로그래머에게 제공되는 경고가 정확합니다. 확인해야합니다!

코드의 메인 브랜치가 널 입력 불가능 유형을 리턴하는 널 입력 가능 유형을 강제로 리턴하는 경우가 많이 있습니다. 경고는 이러한 내용을 명확하게하는 데 도움이됩니다. 즉.

MyClass? x = (new List<MyClass>()).FirstOrDefault(i=>i==1);

널이 불가능한 코드 작성 방법은 널이 있었을 경우 예외를 발생시킵니다. 파싱, 가져 오기 또는 첫 번째 여부

MyClass x = (new List<MyClass>()).First(i=>i==1);

나는 생각하지 않는다 FirstOrDefault반환 값의 nullness이 때문에, 비교 될 수 있습니다 주요 신호. 에서 TryParse메소드 리턴 값이 참일 경우는 null 아니라 아웃 파라미터있어서 계약의 일부이다.
Sebastian Redl

계약의 일부가 아닙니다. 유일하게 보장 된 것은 out 매개 변수에 할당 된 것입니다
Ewan

TryParse방법 에서 기대하는 동작 입니다. 경우 IPAddress.TryParse지금까지 사실 반환하지만 출력 매개 변수에 null 이외의 할당을하지 않았다, 나는 버그로보고있다.
Sebastian Redl

당신의 기대는 이해할 만하지 만 컴파일러에 의해 시행되지는 않습니다. 따라서 IpAddress의 스펙은 true와 null을 반환하지 않는다고 말할 수 있지만 JsonObject 예제는 null 반환이 올바른 경우를 보여줍니다.
Ewan

"당신의 기대는 이해할 만하지 만 컴파일러에 의해 시행되지는 않습니다." -알지만, 내 질문은 다른 코드의 계약이 아니라 내가 염두에 둔 계약을 표현하기 위해 코드를 작성하는 가장 좋은 방법입니다.
Sebastian Redl
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.