C# 8.0의 #nulable 관련 특성을 .NET Framework 프로젝트에서 사용하는 방법
C# 8.0의 신규 문법 중에서 .NET Core 3.0이 아닌 .NET Framework에서 사용할 수 없는 구문이 4가지입니다.
- 기본 인터페이스 메서드
- 비동기 스트림
- #nullable 지시자와 nullable 참조 형식
- 새로운 연산자 - 인덱스, 범위
이 중에서 1번은 .NET Core 3.0에서만 가능하고, 2번은 NuGet으로부터 System.Interactive.Async 어셈블리를 참조 추가하면 되고, 나머지 2개는 개발자가 타입을 정의해 주면 사용할 수 있습니다. 이 중에서 4번에 대해서는 설명한 적이 있는데,
C# 8.0의 Index/Range 연산자를 .NET Framework에서 사용하는 방법 및 비동기 스트림의 컴파일 방법
; https://www.sysnet.pe.kr/2/0/11835
Visual Studio 2019 Preview 4/RC - C# 8.0 Missing compiler required member 'System.Range..ctor'
; https://www.sysnet.pe.kr/2/0/11836
이번에는 3번에 대해서 설명하려고 합니다. ^^
우선 이를 위해 C# 8.0 컴파일러를 활성화하고,
.NET Framework 프로젝트에서 C# 8.0 컴파일러를 사용하는 방법
; https://www.sysnet.pe.kr/2/0/12033
소스 코드에 다음과 같이 null 체크에 대한 NotNullWhen 특성을 사용하면,
#nullable enable
using System.Diagnostics.CodeAnalysis;
class Program
{
public string? Name = "";
static void Main(string[] args)
{
Program pg = new Program();
GetLengthOfName(pg);
}
static int GetLengthOfName(Program person)
{
/* IsNull 코드를 빼면, 그 아래의 person.Name.Length에서 null 참조 경고 발생 */
if (IsNull(person.Name))
{
return 0;
}
return person.Name.Length;
}
static bool IsNull([NotNullWhen(false)] string? value)
{
if (value == null)
{
return true;
}
return false;
}
}
NotNullWhen 타입(및 그 외 8개의 특성)이,
Update libraries to use nullable reference types and communicate nullable rules to callers
; https://learn.microsoft.com/en-us/dotnet/csharp/nullable-attributes
.NET Framework 4.8 BCL에는 포함되어 있지 않으므로 컴파일 오류가 발생합니다. 다행히 특성에 불과하기 때문에 다음과 같이 관련 타입들을 추가해 주면 .NET Framework 용 프로젝트에서도 사용할 수 있습니다.
// Attributes for nullable annotations
// https://github.com/dotnet/corefx/issues/37826
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class AllowNullAttribute : Attribute { }
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class DisallowNullAttribute : Attribute { }
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class MaybeNullAttribute : Attribute { }
/// <summary>Specifies that an output will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class NotNullAttribute : Attribute { }
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class MaybeNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter may be null.
/// </param>
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class NotNullIfNotNullAttribute : Attribute
{
/// <summary>Initializes the attribute with the associated parameter name.</summary>
/// <param name="parameterName">
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
/// </param>
public NotNullIfNotNullAttribute(string parameterName)
{
ParameterName = parameterName ?? throw new ArgumentNullException(nameof(parameterName));
}
/// <summary>Gets the associated parameter name.</summary>
public string ParameterName { get; }
}
/// <summary>Applied to a method that will never return under any circumstance.</summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class DoesNotReturnAttribute : Attribute { }
/// <summary>Specifies that the method will not return if the associated Boolean property is passed the specified value.</summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public sealed class DoesNotReturnIfAttribute : Attribute
{
/// <summary>Initializes the attribute.</summary>
/// <param name="parameterValue">
/// The condition parameter value. Code after the method will be unreachable if the argument to the associated parameter
/// matches this value.
/// </param>
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
/// <summary>Gets the condition parameter value.</summary>
public bool ParameterValue { get; }
}
}
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]