Skip to main content

Command Palette

Search for a command to run...

C# 11 기능 정리

Published
7 min read
C# 11 기능 정리

.NET 7이 미리보기 릴리스가 종료되면서 C# 11 기능이 확정되었습니다. 이에 따라 C# 11 기능을 대해 정리합니다.

C# 11 기능 목록

새롭게 추가된 C# 11의 기능은 다음과 같습니다.

C# 11 기능

파일 로컬 타입 (File-local Types)

파일 내에서만 유효한 타입을 정의할 수 있습니다.

// FileLocalType.cs
file class FileLocalType
{
}

// Program.cs
var s = new FileLocalType();
// 오류    CS0246    'FileLocalType' 형식 또는 네임스페이스 이름을 찾을 수 없습니다. using 지시문 또는 어셈블리 참조가 있는지 확인하세요.

ref 필드 (ref fields)

Span<T>ref 필드 _reference 처럼

 public readonly ref struct Span<T>
    {
        /// <summary>A byref or a native ptr.</summary>
        internal readonly ref T _reference;
        /// <summary>The number of elements this Span contains.</summary>
        private readonly int _length;
        ...

이제 일반 개발자도 ref 필드를 만들 수 있게 되었습니다.

ref 필드는 스택 공간에만 존재할 수 있으므로 ref struct에서만 사용할 수 있습니다.

ref struct Vector2D
{
    public ref int X;
    public ref int Y;
}

required 멤버 (Required members)

이제 required 키워드로 필수 멤버를 만들 수 있습니다.

public class User
{
    public required string Name { get; init; }
    public required int Age { get; init; }
}
var user1 = new User();
// 오류    CS9035    필수 구성원 'User.Age'은(는) 개체 이니셜라이저 또는 특성 생성자에서 설정해야 합니다.
// 오류    CS9035    필수 구성원 'User.Name'은(는) 개체 이니셜라이저 또는 특성 생성자에서 설정해야 합니다.

var user2 = new User
{
    Name = "dimohy",
    Age = 45
};

정적 추상 멤버 (DIM for Static Members)

이제 정적 속성 및 메소드 역시 인터페이스로 구현을 강제할 수 있습니다.

public interface IValue<T>
{
    static abstract T Empty { get; }
}

public class NumValue : IValue<NumValue>
{
    public static NumValue Empty { get; } = new(0);

    private int _value;

    public NumValue(int value) => _value = value;
}

이를 통해 이제 제네릭 형식 제약 조건 where를 이용해 정적 속성 및 메소드를 사용하는 기능을 구현할 수 있습니다.

var emptyValue = GetEmptyValue(new NumValue(100));

T GetEmptyValue<T>(T value) where T : IValue<T>
{
    return T.Empty;
}

숫자 IntPtr (Numberic IntPtr)

IntPtr/UIntPtr과 플랫폼 의존적인 정수 타입 nint / nuint이 플랫폼 의존적인 동일한 정수형 데이터 값임에도 불구하고 다르게 취급되어 정수형에서 제공하는 연산을 제공하지 않았습니다.

이제 두 타입은 동일한 타입으로 처리됩니다. 즉, nint와 nuint는 IntPtr과 UIntPtr의 alias이고 동일한 타입이 됩니다.

부호 없는 오른쪽 시프트 연산자 (Unsigned right shift operator)

Java에도 존재하는 uinsiged right shift 연산자를 이제 C#에서도 제공합니다.

int n = -2020987651; // 0b10000111100010100010110011111101

Console.WriteLine($"[n] \t\t {Convert.ToString(n, 2).PadLeft(32, '0')}");
Console.WriteLine($"[n] >>> 4 ==> \t {Convert.ToString(n >>> 4, 2).PadLeft(32, '0')}");
Console.WriteLine($"[n] >> 4 ==> \t {Convert.ToString(n >> 4, 2).PadLeft(32, '0')}");
[n]              10000111100010100010110011111101
[n] >>> 4 ==>    00001000011110001010001011001111
[n] >> 4 ==>     11111000011110001010001011001111

UTF8 문자열 리터럴 (Utf8 String Literals)

이제 u8 접미사를 통해 UTF8 문자열 리터럴을 만들 수 있습니다.

ReadOnlySpan<byte> utf8Text = "디모이"u8; // 또는
var utf8Text = "디모이"u8;

UTF8 문자열 리터럴은 ReadOnlySpan<byte> 타입이므로 일반 문자열 리터럴 처럼 상수로 취급되지는 않습니다. 또한 ReadOnlySpan<byte> 타입이 스택에만 존재할 수 있으므로 비동기 메서드에서 사용할 수 없는 제약이 존재합니다.

ReadOnlySpan<char>의 패턴 매칭 (Pattern matching on ReadOnlySpan<char>)

이제 패턴 매칭에서 ReadOnlySpan<char>를 문자열과 매칭시킬 수 있습니다.

ReadOnlySpan<char> text = "디모이";

switch (text)
{
    case "디모이":
        break;
    default:
        break;
}

checked 연산자 (Checked Operators)

이제 overflow 제어에 필요한 checked/unchecked 동작을 직접 처리할 수 있게 됩니다.

// 출처 - 성태의 닷넷 이야기
public static Int3 operator checked ++(Int3 lhs)
{
    if (lhs.value + 1 > 8388607)
    {
        throw new OverflowException((lhs.value + 1).ToString());
    }

    return new Int3(lhs.value + 1);
}

public static Int3 operator ++(Int3 lhs) => new Int3(lhs.value + 1);

자동 기본 구조체 (auto-default structs)

이제 구조체에서 필드를 초기화 하지 않아도 자동으로 default으로 초기화 됩니다.

// C# 11 이전에는 초기화 관련 오류 발생
public struct S
{
    public int x, y;
    public S()
    {
    }
}

보간의 개행 (Newlines in interpolations)

이제 보간에서 개행이 가능합니다.

var v = $"Count is\t: { this.Is.A.Really()
                            .That.I.Should(
                                be + able)[
                                    to.Wrap()] }.";

목록 패턴 (List patterns)

이제 목록 패턴이 지원됩니다.

int[] arr = { 1, 2, 3, 4, 5, 6 };

var result = arr switch
{
    //[1, .., 6] => 1,
    [1, .., 5, _] => 2,
    _ => 0
};
Console.WriteLine(result);

원시 문자열 리터럴 (Raw string literals)

이제 원시 문자열 리터럴을 사용할 수 있습니다.

var rawText = """
    Line 1, value 1
    Line 2, value 2
    Line 3, value 3
    """;

Console.WriteLine(rawText);

마지막 닫는 """ 위치에 맞게 문자열로 잘 대입됩니다.

Line 1, value 1
Line 2, value 2
Line 3, value 3

만약 문자열 안에 """가 포함되어 있을 경우, 열고 닫는 큰따옴표를 """"로 할 수 있습니다.

var rawText = """"
    Line 1, value 1
    Line 2, value 2
    Line 3, value 3
    """Text"""
    """";

Console.WriteLine(rawText);
Line 1, value 1
Line 2, value 2
Line 3, value 3
"""Text"""

또한 문자열 보간도 사용할 수 있습니다.

var num = 1;
var rawText = $""""
    Line 1, value {num}
    Line 2, value {num + 1}
    Line 3, value {num + 2}
    """Text"""
    """";

Console.WriteLine(rawText);
Line 1, value 1
Line 2, value 2
Line 3, value 3
"""Text"""

그런데 JSON 문자열 처럼 문자열에 { 또는 }이 있을 경우 $ 대신 $$을, {value} 대신 {{value}}으로 사용할 수 있습니다.

var num = 1;
var rawText = $$""""
    {
    Line 1, value {{num}}
    Line 2, value {{num + 1}}
    Line 3, value {{num + 2}}
    }
    """Text"""
    """";

Console.WriteLine(rawText);
{
Line 1, value 1
Line 2, value 2
Line 3, value 3
}
"""Text"""

정적 메소드 그룹에 대한 캐시 대리자 (Cache delegates for static method group)

클로저가 없는 정적 메소드 그룹에 대해서 C# 11 이전에는 호출할 때마다 대리자의 새 인스턴스를 만들었습니다.

void Demo(Func<string> action) { }
string GetString() => "";

```csharp
// C# code
Demo(GetString); // -> Demo(new Func<string>(GetString));

이제 클로저가 없으면 컴파일러는 대리자를 캐시하고 각 호출에서 재사용 합니다.

nameof(parameter)

이제 매개변수의 이름 역시 nameof(parameter) 형태로 사용할 수 있게 되었습니다.

Assert(1 == 1);
Assert("A" == "B");


void Assert(bool condition, [CallerArgumentExpression(nameof(condition))] string? message = default)
{
    Console.WriteLine(nameof(condition));
    Console.WriteLine(message);
}
condition
1 == 1
condition
"A" == "B"

시프트 연산자의 제약 완화 (Relaxing Shift Operator)

이제 시프트 연산자의 타입 완화로 다른 타입을 연산자 매개변수로 받을 수 있게 되었습니다.

// 출처 - 성태의 닷넷 이야기 
C c = new C();
C d = c << 5.8f;

class C
{
    public static C operator << (C c, float o)
    {
        Console.WriteLine("shift operator called with " + o);

        return c;
    }
}

제네릭 특성 (Generic attributes)

이제 특성 타입에 제네릭을 사용할 수 있게 되었습니다.

class ValueAttribute<TValue> : Attribute
    where TValue : struct
{
    public TValue V { get; set; }
}

[Value<int>(V = 10)]
class TestClass
{

}

참고 자료

좀 더 상세한 내용은 아래의 문서를 확인하세요.

67 views

More from this blog

개발, 테스트, 운영에서의 도커 활용

핵심 원칙: "한 번 빌드하고, 어디서든 실행한다 (Build once, run anywhere)" 도커의 가장 큰 장점은 환경 일관성입니다. 동일한 도커 이미지를 사용하여 개발, 테스트, 운영 환경을 구성함으로써 "제 PC에서는 됐는데..." 하는 문제를 최소화할 수 있습니다. 1. 개발 단계 (Development) 목표: 빠른 코드 변경 반영, 쉬운 디버깅, 실제 운영 환경과 유사한 환경 구성. Docker 사용 방안: Dockerf...

May 9, 20256 min read15

[EF Core] 데이터 삭제 시 소프트 삭제 적용

DB에서 데이터를 삭제하면 일반적으로 복구할 수 없습니다. 또한 관계에 따라 영구 삭제 자체가 어려울 수도 있습니다. 그래서 데이터를 영구 삭제하는 대신 IsDeleted 속성을 true로 주고 IsDeleted 속성을 필터링해서 조회하는 방법을 사용하기도 합니다. 이를 소프트 삭제라고 합니다. 그런데 EF에서 알아서 데이터 삭제 시 소프트 삭제를 하고 쿼리시 IsDeleted 속성을 체크해서 삭제한 데이터를 제외한 데이터만 쿼리하게 하는 ...

Mar 18, 20243 min read19

[EF Core] ValueConverter를 이용해서 엔터티 속성의 도메인 관리

EF Core를 사용하면서 문자열 길이 등의 특성을 일일이 지정하는 것은 번거롭습니다. ... [MaxLength(32)] public string? 제목 { get; set; } 엔터티가 한 개일 때는 상관이 없으나 제목 유형이 여러 엔터티에 사용될 경우 유형을 지정하기 번거롭습니다. 속성 유형을 도메인으로 관리하면 참 편할텐데요, ValueConverter를 이용할 수 있습니다. 그런데 이것을 인터페이스 정적 추상를 사용해서 다음처럼 ...

Mar 16, 20242 min read8

디모이 블로그

154 posts

.NET 관련 기술을 선호하고 새로운 언어를 배우는데 관심이 있습니다.

C# 11 기능 정리