C# 11 기능 정리

C# 11 기능 정리

·

7 min read

.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
{

}

참고 자료

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