기본 제공 참조 형식(C# 참조)

C#에는 많은 기본 제공 참조 형식이 있습니다. .NET 라이브러리의 형식에 대한 동의어인 키워드 또는 연산자를 가지고 있습니다.

개체 유형

object 형식은 .NET에서 System.Object의 별칭입니다. C#의 통합 형식 시스템에서 사용자 정의 및 미리 정의된 참조 형식과 값 형식을 비롯한 모든 형식은 직접 또는 간접적으로 System.Object에서 상속합니다. object 형식의 변수에 모든 형식의 값을 할당할 수 있습니다. 모든 object 변수는 리터럴 null을 사용하여 기본값으로 할당할 수 있습니다. 값 형식의 변수가 개체로 변환되면 boxed라고 합니다. object 형식의 변수가 값 형식으로 변환되면 unboxed라고 합니다. 자세한 내용은 boxing 및 unboxing을 참조하세요.

문자열 유형

string 형식은 0자 이상의 유니코드 문자 시퀀스를 나타냅니다. string는 .NET에서 System.String의 별칭입니다.

string은 참조 형식이지만 같음 연산자 ==!=는 참조가 아니라 string 개체의 값을 비교하도록 정의됩니다. 값 기반 같음은 문자열 같음 테스트를 보다 직관적으로 만듭니다. 예시:

string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));

이전 예제에서는 문자열의 내용이 동일하지만 ab(이)가 동일한 문자열 인스턴스를 참조하지 않으므로 "True" 및 "False"를 표시합니다.

+ 연산자는 문자열을 연결합니다.

string a = "good " + "morning";

앞의 코드는 "good morning"을 포함하는 문자열 개체를 만듭니다.

문자열은 변경이 불가하여 문자열 개체를 만든 후에는 문자열 개체의 내용을 변경할 수 없습니다. 예를 들어 이 코드를 작성하면 컴파일러는 실제로 새 문자 시퀀스를 보유할 새 문자열 개체를 만들고, 새 개체가 b에 할당됩니다. b에 할당된 메모리(문자열 "h"가 포함된 경우)는 가비지 수집에 적합합니다.

string b = "h";
b += "ello";

[]연산자는 문자열의 개별 문자에 대한 읽기 전용 액세스에 사용할 수 있습니다. 유효한 인덱스는 0에서 시작되고 문자열의 길이보다 작아야 합니다.

string str = "test";
char x = str[2];  // x = 's';

비슷한 방식으로 [] 연산자도 문자열의 각 문자를 반복하는 데 사용할 수 있습니다.

string str = "test";

for (int i = 0; i < str.Length; i++)
{
  Console.Write(str[i] + " ");
}
// Output: t e s t

문자열 리터럴

문자열 리터럴은 string 형식이며 원시, 따옴표 및 축자의 세 가지 형식으로 작성할 수 있습니다.

원시 문자열 리터럴은 C# 11부터 사용할 수 있습니다. 원시 문자열 리터럴은 이스케이프 시퀀스를 필요로 하지 않고 임의의 텍스트를 포함할 수 있습니다. 원시 문자열 리터럴에는 공백과 새 줄, 포함된 따옴표 및 기타 특수 문자가 포함될 수 있습니다. 원시 문자열 리터럴은 최소 세 개의 큰따옴표(""")로 묶입니다.

"""
This is a multi-line
    string literal with the second line indented.
"""

세 개 이상의 큰따옴표 문자 시퀀스를 포함할 수도 있습니다. 텍스트에 포함된 따옴표 시퀀스가 필요한 경우 필요에 따라 더 많은 따옴표로 원시 문자열 리터럴을 시작하고 끝냅니다.

"""""
This raw string literal has four """", count them: """" four!
embedded quote characters in a sequence. That's why it starts and ends
with five double quotes.

You could extend this example with as many embedded quotes as needed for your text.
"""""

원시 문자열 리터럴에는 일반적으로 포함된 텍스트와 별도의 줄에 시작 및 끝 따옴표 시퀀스가 있습니다. 여러 줄 원시 문자열 리터럴은 자체 따옴표로 묶인 문자열을 지원합니다.

var message = """
"This is a very important message."
""";
Console.WriteLine(message);
// output: "This is a very important message."

시작 및 끝 따옴표가 별도의 줄에 있는 경우 여는 따옴표와 끝 따옴표 앞의 줄 바꿈은 최종 내용에 포함되지 않습니다. 닫는 따옴표 시퀀스는 문자열 리터럴의 맨 왼쪽 열을 나타냅니다. 전체 코드 형식과 일치하도록 원시 문자열 리터럴을 들여쓰기할 수 있습니다.

var message = """
    "This is a very important message."
    """;
Console.WriteLine(message);
// output: "This is a very important message."
// The leftmost whitespace is not part of the raw string literal

끝 따옴표 시퀀스의 오른쪽에 있는 열은 유지됩니다. 이 동작을 사용하면 다음 예제와 같이 JSON, YAML 또는 XML과 같은 데이터 형식에 원시 문자열을 사용할 수 있습니다.

var json= """
    {
        "prop": 0
    }
    """;

텍스트 줄이 닫는 따옴표 시퀀스의 왼쪽으로 확장되면 컴파일러에서 오류가 발생합니다. 여는 따옴표 시퀀스와 닫는 따옴표 시퀀스는 동일한 줄에 있을 수 있으며 문자열 리터럴은 따옴표 문자로 시작하거나 끝나지 않습니다.

var shortText = """He said "hello!" this morning.""";

원시 문자열 리터럴을 문자열 보간과 결합하여 출력 문자열에 따옴표 문자와 중괄호를 포함할 수 있습니다.

따옴표가 있는 문자열 리터럴은 큰따옴표(")로 묶여 있습니다.

"good morning"  // a string literal

문자열 리터럴에는 모든 문자 리터럴이 포함될 수 있습니다. 이스케이프 시퀀스가 포함됩니다. 다음 예제에서는 이스케이프 시퀀스 \\를 백슬래시에 사용하고, \u0066을 f에 사용하고, \n을 줄 바꿈에 사용합니다.

string a = "\\\u0066\n F";
Console.WriteLine(a);
// Output:
// \f
//  F

참고 항목

이스케이프 코드 \udddd(여기서 dddd는 4자리 숫자)는 유니코드 문자 U+dddd를 나타냅니다. 8자리 유니코드 이스케이프 코드 \Udddddddd도 인식됩니다.

축자 문자열 리터럴@로 시작하며 큰따옴표로 묶여 있습니다. 예시:

@"good morning"  // a string literal

축자 문자열의 장점은 이스케이프 시퀀스가 처리되지 않으므로 쉽게 작성할 수 있다는 것입니다. 예를 들어 다음 텍스트는 정규화된 Windows 파일 이름과 일치합니다.

@"c:\Docs\Source\a.txt"  // rather than "c:\\Docs\\Source\\a.txt"

@-따옴표로 묶인 문자열에 큰따옴표를 포함하려면 다음과 같이 두 배로 지정합니다.

@"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.

UTF-8 문자열 리터럴

.NET의 문자열은 UTF-16 인코딩을 사용하여 저장됩니다. UTF-8은 웹 프로토콜 및 기타 중요한 라이브러리의 표준입니다. C# 11부터 문자열 리터럴에 u8 접미사를 추가하여 UTF-8 인코딩을 지정할 수 있습니다. UTF-8 리터럴은 ReadOnlySpan<byte> 개체로 저장됩니다. UTF-8 문자열 리터럴의 자연 형식은 ReadOnlySpan<byte>입니다. UTF-8 문자열 리터럴을 사용하면 다음 코드와 같이 동등한 System.ReadOnlySpan<T> 선언보다 더 명확한 선언이 만들어집니다.

ReadOnlySpan<byte> AuthWithTrailingSpace = new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 };
ReadOnlySpan<byte> AuthStringLiteral = "AUTH "u8;

UTF-8 문자열 리터럴을 배열로 저장하려면 ReadOnlySpan<T>.ToArray()(을)를 사용하여 리터럴이 포함된 바이트를 변경 가능한 배열에 복사해야 합니다.

byte[] AuthStringLiteral = "AUTH "u8.ToArray();

UTF-8 문자열 리터럴은 컴파일 시간 상수가 아닌 런타임 상수입니다. 따라서 선택적 매개 변수의 기본값으로 사용할 수 없습니다. UTF-8 문자열 리터럴은 문자열 보간과 결합할 수 없습니다. 동일한 문자열 식에는 $ 토큰과 u8 접미사를 사용할 수 없습니다.

대리자 형식

delegate 형식의 선언은 메서드 시그니처와 유사합니다. 반환 값이 있으며 모든 형식의 매개 변수를 개수에 관계없이 사용할 수 있습니다.

public delegate void MessageDelegate(string message);
public delegate int AnotherDelegate(MyType m, long num);

.NET에서 System.ActionSystem.Func 유형은 많은 일반 대리자에 대한 일반적인 정의를 제공합니다. 새 사용자 지정 대리자 형식을 정의할 필요가 없습니다. 대신 제공된 제네릭 형식의 인스턴스화를 만들 수 있습니다.

delegate는 명명된 메서드나 무명 메서드를 캡슐화하는 데 사용할 수 있는 참조 형식입니다. 대리자는 C++의 함수 포인터와 비슷하지만 형식 안전성과 보안성을 제공한다는 점이 다릅니다. 대리자 적용에 대해서는 대리자제네릭 대리자를 참조하세요. 대리자는 이벤트의 기반이 됩니다. 대리자는 명명된 메서드나 무명 메서드와 연결하여 인스턴스화할 수 있습니다.

대리자는 호환되는 반환 형식 및 입력 매개 변수가 있는 메서드나 람다 식을 사용하여 인스턴스화해야 합니다. 메서드 시그니처에서 허용되는 가변성 수준에 대한 자세한 내용은 대리자의 가변성을 참조하세요. 무명 메서드에서 사용하기 위해 메서드에 연결할 대리자와 코드를 함께 선언합니다.

런타임에 관련된 대리자 형식이 변형 변환으로 인해 다른 경우 런타임 예외로 인해 대리자 조합 또는 제거가 실패합니다. 다음 예제에서는 실패하는 상황을 보여 줍니다.

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Valid due to implicit reference conversion of
// objectAction to Action<string>, but may fail
// at run time.
Action<string> combination = stringAction + objectAction;

새 대리자 개체를 만들어 올바른 런타임 형식으로 대리자를 만들 수 있습니다. 다음 예제에서는 이러한 해결 방법을 앞의 예제에 적용할 수 있는 방법을 보여줍니다.

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Creates a new delegate instance with a runtime type of Action<string>.
Action<string> wrappedObjectAction = new Action<string>(objectAction);

// The two Action<string> delegate instances can now be combined.
Action<string> combination = stringAction + wrappedObjectAction;

비슷한 구문을 사용하는 함수 포인터를 선언할 수 있습니다. 함수 포인터는 대리자 형식을 인스턴스화하고 가상 Invoke 메서드를 호출하는 대신 calli 명령을 사용합니다.

동적 형식

dynamic 유형은 변수 및 해당 멤버에 대한 참조 사용이 컴파일 파일 형식 검사를 바이패스함을 나타냅니다. 대신, 이러한 작업은 런타임에 확인됩니다. dynamic 형식은 Office Automation API와 같은 COM API, IronPython 라이브러리 등의 동적 API 및 HTML DOM(문서 개체 모델)에 대한 액세스를 간소화합니다.

dynamic 형식은 대부분의 상황에서 object 형식처럼 동작합니다. 특히 null이 아닌 모든 식은 dynamic 형식으로 변환될 수 있습니다. dynamic 형식의 식을 포함하는 작업이 확인되지 않거나 컴파일러에서 형식을 확인한다는 점에서 형식 dynamic은(는) 형식 object(와)과 다릅니다. 컴파일러는 작업에 대한 정보를 패키지하며, 나중에 해당 정보는 런타임에 작업을 평가하는 데 사용됩니다. 이 과정에서 dynamic 형식의 변수는 object 형식의 변수로 컴파일됩니다. 따라서 dynamic 형식은 컴파일 시간에만 존재하고 런타임에는 존재하지 않습니다.

다음 예제에서는 dynamic 형식의 변수와 object 형식의 변수를 비교합니다. 컴파일 시간에 각 변수의 형식을 확인하려면 WriteLine 문의 dyn 또는 obj 위에 마우스 포인터를 놓습니다. IntelliSense를 사용할 수 있는 편집기로 다음 코드를 복사합니다. IntelliSense는 dyn에 대해 dynamic을 표시하고 obj에 대해 object를 표시합니다.

class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

WriteLine 문은 dynobj의 런타임 형식을 표시합니다. 이 시점에는 둘 다 동일한 형식인 정수입니다. 다음 출력이 생성됩니다.

System.Int32
System.Int32

컴파일 시간에 dynobj 간의 차이를 보려면 앞의 예제에서 선언과 WriteLine 문 사이에 다음 두 줄을 추가합니다.

dyn = dyn + 3;
obj = obj + 3;

obj + 3 식에 정수와 개체를 추가하려는 시도와 관련해서 컴파일러 오류가 보고됩니다. 하지만 dyn + 3에 대한 오류는 보고되지 않습니다. dyn(이)가 포함된 식은 dyn의 형식이 dynamic이기 때문에 컴파일 시간에 확인되지 않습니다.

다음 예제에서는 여러 선언에 dynamic을 사용합니다. 또한 Main 메서드는 컴파일 시간 형식 검사를 런타임 형식 검사와 비교합니다.

using System;

namespace DynamicExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            ExampleClass ec = new ExampleClass();
            Console.WriteLine(ec.ExampleMethod(10));
            Console.WriteLine(ec.ExampleMethod("value"));

            // The following line causes a compiler error because ExampleMethod
            // takes only one argument.
            //Console.WriteLine(ec.ExampleMethod(10, 4));

            dynamic dynamic_ec = new ExampleClass();
            Console.WriteLine(dynamic_ec.ExampleMethod(10));

            // Because dynamic_ec is dynamic, the following call to ExampleMethod
            // with two arguments does not produce an error at compile time.
            // However, it does cause a run-time error.
            //Console.WriteLine(dynamic_ec.ExampleMethod(10, 4));
        }
    }

    class ExampleClass
    {
        static dynamic _field;
        dynamic Prop { get; set; }

        public dynamic ExampleMethod(dynamic d)
        {
            dynamic local = "Local variable";
            int two = 2;

            if (d is int)
            {
                return local;
            }
            else
            {
                return two;
            }
        }
    }
}
// Results:
// Local variable
// 2
// Local variable

C# 언어 사양

자세한 내용은 C# 언어 사양의 다음 섹션을 참조하세요.

참고 항목