다음을 통해 공유


사용자 지정 특성 정의 및 읽기

특성은 선언적 방법으로 코드와 정보를 연결 하는 방법을 제공 합니다. 또한 다양한 대상에 적용할 수 있는 재사용 가능한 요소를 제공할 수도 있습니다. ObsoleteAttribute을(를) 고려하십시오. 클래스, 구조체, 메서드, 생성자 등에 적용할 수 있습니다. 요소가 사용되지 않음을 선언 합니다. 그런 다음 C# 컴파일러에서 이 특성을 찾고 이에 대한 응답으로 몇 가지 작업을 수행합니다.

이 자습서에서는 코드에 특성을 추가하는 방법, 고유한 특성을 만들고 사용하는 방법 및 .NET에 기본 제공되는 일부 특성을 사용하는 방법을 알아봅니다.

필수 조건

.NET을 실행하려면 컴퓨터를 설정해야 합니다. .NET 다운로드 페이지에서 설치 지침을 찾을 수 있습니다. Windows, Ubuntu Linux, macOS 또는 Docker 컨테이너에서 이 애플리케이션을 실행할 수 있습니다. 즐겨 찾는 코드 편집기를 설치해야 합니다. 다음 설명에서는 오픈 소스 플랫폼 간 편집기인 Visual Studio Code를 사용합니다. 그러나 당신이 편안하게 사용할 수 있는 도구를 선택할 수 있습니다.

앱 만들기

이제 모든 도구를 설치했으므로 새 .NET 콘솔 앱을 만듭니다. 명령줄 생성기를 사용하려면 즐겨 찾는 셸에서 다음 명령을 실행합니다.

dotnet new console

이 명령은 베어본 .NET 프로젝트 파일을 만듭니다. 이 프로젝트를 컴파일하는 데 필요한 종속성을 복원하기 위해 실행 dotnet restore 합니다.

dotnet restore, dotnet new, dotnet build, dotnet run, dotnet testdotnet publish같은 복원이 필요한 모든 명령에서 암시적으로 실행되므로 dotnet pack 실행할 필요가 없습니다. 암시적 복원을 사용하지 않도록 설정하려면 --no-restore 옵션을 사용합니다.

dotnet restore 명령은 Azure DevOps Services의 연속 통합 빌드 또는 복원이 언제 발생할지를 명시적으로 제어해야 하는 빌드 시스템처럼 명시적인 복원이 적합한 특정 시나리오에서 여전히 유용합니다.

NuGet 피드를 관리하는 방법에 대한 자세한 내용은 dotnet restore 설명서참조하세요.

프로그램을 실행하려면 .를 사용합니다 dotnet run. 콘솔에 "Hello, World" 출력이 표시됩니다.

코드에 특성 추가

C#에서 특성은 기본 클래스에서 Attribute 상속되는 클래스입니다. 상속되는 모든 클래스는 다른 코드 조각에서 Attribute 일종의 "태그"로 사용할 수 있습니다. 예를 들어, ObsoleteAttribute라는 속성이 있습니다. 이 특성은 코드가 사용되지 않으며 더 이상 사용해서는 안 됨을 알 수 있습니다. 예를 들어 대괄호를 사용하여 클래스에 이 특성을 배치합니다.

[Obsolete]
public class MyClass
{
}

클래스를 ObsoleteAttribute라고 부르지만, 코드에서는 [Obsolete]만 사용하면 됩니다. 대부분의 C# 코드는 이 규칙을 따릅니다. 선택하는 경우 전체 이름을 [ObsoleteAttribute] 사용할 수 있습니다.

사용되지 않는 클래스를 표시할 때는 사용되지 않는 이유 및/또는 대신 사용할 내용 에 대한 몇 가지 정보를 제공하는 것이 좋습니다. 이 설명을 제공하기 위해 Obsolete 속성에 문자열 매개변수를 포함합니다.

[Obsolete("ThisClass is obsolete. Use ThisClass2 instead.")]
public class ThisClass
{
}

문자열은 ObsoleteAttribute 생성자의 인수로 전달되며, 이는 마치 var attr = new ObsoleteAttribute("some string")를 작성하는 것과 같습니다.

특성 생성자에 대한 매개 변수는 단순 형식/리터럴 bool, int, double, string, Type, enums, etc 및 해당 형식의 배열로 제한됩니다. 식이나 변수는 사용할 수 없습니다. 위치 또는 명명된 매개 변수를 자유롭게 사용할 수 있습니다.

고유한 특성 만들기

기본 클래스에서 Attribute 상속되는 새 클래스를 정의하여 특성을 만듭니다.

public class MySpecialAttribute : Attribute
{
}

이전 코드를 사용하면 [MySpecial] 또는 [MySpecialAttribute]을 코드의 다른 위치에서 속성으로 사용할 수 있습니다.

[MySpecial]
public class SomeOtherClass
{
}

.NET 기본 클래스 라이브러리의 특성(예: ObsoleteAttribute 컴파일러 내의 특정 동작 트리거). 그러나 사용자가 만드는 모든 특성은 메타데이터로만 작동하며 특성 클래스 내의 코드가 실행되지는 않습니다. 코드의 다른 위치에서 해당 메타데이터에 대해 작업하는 것은 사용자에게 달려 있습니다.

조심해야 할 'gotcha'가 있습니다. 앞에서 설명한 것처럼 특성을 사용할 때 특정 형식만 인수로 전달될 수 있습니다. 그러나 특성 형식을 만들 때 C# 컴파일러가 해당 매개 변수를 만드는 것을 막지는 않습니다. 다음 예제에서는 올바르게 컴파일되는 생성자를 사용하여 특성을 만들었습니다.

public class GotchaAttribute : Attribute
{
    public GotchaAttribute(Foo myClass, string str)
    {
    }
}

그러나 특성 구문과 함께 이 생성자를 사용할 수 없습니다.

[Gotcha(new Foo(), "test")] // does not compile
public class AttributeFail
{
}

위의 코드로 인해 다음과 같은 컴파일러 오류가 발생합니다. Attribute constructor parameter 'myClass' has type 'Foo', which is not a valid attribute parameter type

특성 사용량을 제한하는 방법

특성은 다음 "대상"에서 사용할 수 있습니다. 앞의 예제는 클래스에 표시되지만 다음에서도 사용할 수 있습니다.

  • 집회
  • 클래스
  • 생성자
  • 대리인
  • 열거형
  • 이벤트
  • 분야
  • 일반 매개변수
  • 인터페이스
  • 메서드
  • 모듈
  • 매개 변수
  • 재산
  • 리턴값
  • 구조체

특성 클래스를 만들 때 기본적으로 C#을 사용하면 가능한 특성 대상에서 해당 특성을 사용할 수 있습니다. 특성을 특정 대상으로 제한하려면 특성 클래스를 AttributeUsageAttribute 사용하여 이 작업을 수행할 수 있습니다. 맞습니다, 속성에 속성이 있다는 것입니다!

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttributeForClassAndStructOnly : Attribute
{
}

클래스 또는 구조체가 아닌 항목에 위의 특성을 배치하려고 하면 다음과 같은 컴파일러 오류가 발생합니다. Attribute 'MyAttributeForClassAndStructOnly' is not valid on this declaration type. It is only valid on 'class, struct' declarations

public class Foo
{
    // if the below attribute was uncommented, it would cause a compiler error
    // [MyAttributeForClassAndStructOnly]
    public Foo()
    { }
}

코드 요소에 연결된 특성을 사용하는 방법

특성은 메타데이터로 작동합니다. 외적인 힘이 없다면, 그들은 실제로 아무것도 하지 않습니다.

특성을 찾아서 작업하려면 리플렉션이 필요합니다. 리플렉션을 사용하면 C#에서 다른 코드를 검사하는 코드를 작성할 수 있습니다. 예를 들어 리플렉션을 사용하여 클래스에 대한 정보를 가져올 수 있습니다(코드의 머리글에 추가 using System.Reflection; ).

TypeInfo typeInfo = typeof(MyClass).GetTypeInfo();
Console.WriteLine("The assembly qualified name of MyClass is " + typeInfo.AssemblyQualifiedName);

다음과 같이 인쇄됩니다. The assembly qualified name of MyClass is ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

TypeInfo 개체(또는 , MemberInfo또는 FieldInfo다른 개체)가 있으면 메서드를 GetCustomAttributes 사용할 수 있습니다. 이 메서드는 개체 컬렉션을 Attribute 반환합니다. 특성 유형을 사용하고 GetCustomAttribute 지정할 수도 있습니다.

다음은 GetCustomAttributes 인스턴스에서 MemberInfo를 사용하는 예로, 이 MyClass에는 앞에서 본 바와 같이 [Obsolete] 속성이 있습니다.

var attrs = typeInfo.GetCustomAttributes();
foreach(var attr in attrs)
    Console.WriteLine("Attribute on MyClass: " + attr.GetType().Name);

콘솔에 출력됩니다: Attribute on MyClass: ObsoleteAttribute. MyClass에 다른 특성을 추가하세요.

이러한 Attribute 개체는 지연 초기화된다는 점에 유의해야 합니다. 즉, GetCustomAttribute 또는 GetCustomAttributes를 사용할 때까지 인스턴스화되지 않습니다. 또한 매번 인스턴스화됩니다. 행에서 두 번 호출 GetCustomAttributes 하면 두 개의 서로 다른 인스턴스가 반환됩니다 ObsoleteAttribute.

런타임의 일반적인 특성

특성은 많은 도구 및 프레임워크에서 사용됩니다. NUnit은 [Test][TestFixture]과 같은 특성을 사용하며, 이러한 특성은 NUnit 테스트 실행기에서 사용됩니다. ASP.NET MVC는 [Authorize] 같은 특성을 사용하고 MVC 액션에 대한 횡단 관심사를 수행하기 위한 작업 필터 프레임워크를 제공합니다. PostSharp 는 특성 구문을 사용하여 C#에서 측면 지향 프로그래밍을 허용합니다.

.NET Core 기본 클래스 라이브러리에 기본 제공되는 몇 가지 주목할 만한 특성은 다음과 같습니다.

  • [Obsolete]; 이 예제는 위의 예제에서 사용되었으며 네임스페이스에 System 있습니다. 변경 코드 베이스에 대한 선언적 설명서를 제공하는 것이 유용합니다. 문자열 형식으로 메시지를 제공할 수 있으며 컴파일러 경고에서 컴파일러 오류로 에스컬레이션하는 데 다른 부울 매개 변수를 사용할 수 있습니다.
  • [Conditional]; 이 특성은 네임스페이스에 System.Diagnostics 있습니다. 이 특성은 메서드(또는 특성 클래스)에 적용할 수 있습니다. 생성자에 문자열을 전달해야 합니다. 해당 문자열이 지시문과 일치하지 #define 않으면 C# 컴파일러는 해당 메서드에 대한 호출을 제거하지만 메서드 자체는 제거하지 않습니다. 일반적으로 디버깅(진단) 용도로 이 기술을 사용합니다.
  • [CallerMemberName]; 이 특성은 매개 변수에 사용할 수 있으며 네임스페이스에 System.Runtime.CompilerServices 있습니다. CallerMemberName 는 다른 메서드를 호출하는 메서드의 이름을 삽입하는 데 사용되는 특성입니다. 다양한 UI 프레임워크에서 INotifyPropertyChanged를 구현할 때 '매직 문자열'을 제거하는 방법입니다. 예를 들면 다음과 같습니다.
public class MyUIClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    public void RaisePropertyChanged([CallerMemberName] string propertyName = default!)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private string? _name;
    public string? Name
    {
        get { return _name;}
        set
        {
            if (value != _name)
            {
                _name = value;
                RaisePropertyChanged();   // notice that "Name" is not needed here explicitly
            }
        }
    }
}

위의 코드에서는 리터럴 "Name" 문자열이 필요하지 않습니다. 사용은 CallerMemberName 오타 관련 버그를 방지하고 더 원활한 리팩터링/이름 바꾸기를 가능하게 합니다. 특성은 C#에 선언적 기능을 제공하지만 메타 데이터 형식의 코드이며 자체적으로 작동하지 않습니다.