관리 메뉴

제목없음

C# Attribute 적용하기 본문

프로그램 작성/컴퓨터 언어

C# Attribute 적용하기

다람군 2013.05.18 03:09

1. 특성 (Attribute)

C#에는 Attribute라는 독특한 문법이 존재합니다. 특성은 클래스로 작성되며, 클래스, 메서드, 열거형, 속성, 상수 등 거의 대부분에 적용할 수 있습니다.

 

클래스를 Attribute로 사용하기 위해서는 클래스 이름이 Attribute로 끝나야 하며, Attribute 클래스를 상속받아야 합니다.

 

특성은 []을 이용하여 사용하며 아래와 같이 사용할 수 있습니다.

[Obsolete("Do not use this class", true)]
public class MyOldClass
{

}

 

위의 코드에서 [] 사이에 들어있는 것이 특성입니다. 특성 값에 아무 값도 넣지 않을 경우 ()을 생략해도 됩니다. () 안에서 특성의 특정 속성 및 필드에 값을 바로 적용 가능합니다.

 

2. 공용 특성 (Common Attribute)

공용 특성(Common Attribute)으로는 Conditional, Obsolete, AttributeUsage가 있습니다(이 외에 Global Attributes라는 것도 있으나 다음에 다루도록 하겠습니다).

 

2.1. Conditional

Conditional 특성은 아래와 같이 사용합니다.

public class MyClass
{
    [Conditional("DEBUG")]
    public void Write() { Console.Write("a"); }
}

 

Conditional을 적용한 경우, 특성에 넣은 문자열 값과 프로젝트에 정의된 전처리기를 비교하여 해당 문자열 값에 해당하는 전처리기가 정의되어 있는 경우에만 해당 요소를 사용합니다.

 

즉, Conditional 특성을 "DEBUG" 값을 넣어 적용한 경우 디버그 모드에서만 해당 요소를 사용할 수 있고, 릴리즈 모드에서는 동작하지 않게 됩니다. 동작하지 않아도 오류는 발생하지 않습니다.

 

이 특성은 특성 클래스(이름이 Attribute로 끝나는 클래스)와 메서드에만 적용할 수 있으며, 같은 요소에 여러 개의 Conditional을 적용할 수 있습니다.

 

2.2. Obsolete

Obsolete 특성은 아래와 같이 사용합니다.

[Obsolete("Do not use this class", false)]
public class MyOldClass
{
    [Obsolete("Do not use this method", true)]
    public void MyMethod { Console.WriteLine("MyMethod"); }
}

 

Obsolete를 적용한 경우, 특성이 적용된 요소를 사용할 경우 컴파일 타임에 특성에 넣은 참/거짓 값에 따라 경고 또는 오류를 발생시킵니다. 특성에 넣은 문자열 값은 경고 또는 오류 메시지로 사용됩니다.

 

기본 참/거짓 값은 false이며, 특성에 들어가는 값의 순서는 반드시 "경고 또는 오류 메시지" 다음에 경고/오류 값이 들어가야 합니다.

 

이 특성은 클래스, 구조체, 열거형, 생성자, 메서드, 속성, 필드, 이벤트, 인터페이스, 델리게이트에 적용할 수 있으며, 파생 클래스에 특성이 상속됩니다.

 

2.3 AttributeUsage

AttributeUsage 특성은 아래와 같이 사용합니다.

[AttributeUsage(System.AttributeTargets.All, AllowMultiple=true, Inherited=true)]
public class MyCustomAttribute : Attribute
{

}

 

AttributeUsage를 적용한 경우, 특성을 사용할 수 있는 요소, 특성 중복 적용 가능 여부(AllowMultiple), 특성 상속 여부(Inherited)가 해당 특성 클래스에 적용됩니다. 특성을 사용할 수 있는 요소는 반드시 적용해주어야 하며, AllowMultiple과 Inherited는 필요하지 않은 경우 적용하지 않아도 무관합니다.

 

AllowMultiple이 true로 적용된 경우 해당 특성 클래스는 한 요소에 여러번 사용할 수 있게 됩니다.

 

Inherited가 true로 적용된 경우 해당 특성 클래스을 사용한 클래스의 파생 클래스에도 그대로 적용됩니다.

 

이 특성은 특성 클래스(이름이 Attribute로 끝나는 클래스)에만 적용할 수 있으며, 파생 클래스에 특성이 상속됩니다.

 

3. P/Invoke 특성

P/Invoke는 C#과 같은 .NET 언어에서 네이티브 기능을 사용하기 위해 사용하는 기능입니다. Java의 JNA를 생각하시면 이해하기 쉽겠지만 JNA와 같은 번거로운 방법은 아닙니다(어떤 면에서 보면 JNA가 P/Invoke보다 간편하긴 합니다).

 

즉, P/Invoke를 사용하면 외부 라이브러리에서 함수를 끌어다 사용할 수 있으며, 마샬링(Marshal) 기법을 이용하여 자료를 네이티브에 맞게, 또는 .NET 가상 머신에 맞게 변환할 수도 있습니다.

 

P/Invoke는 Windows에서만 사용할 수 있는 기능이 아닙니다. Linux, OS X와 같은 범용 OS에서는 물론이고 Windows Phone, Android, iOS, Blackberry와 같은 모바일 단말기, Silverlight와 같은 웹용 멀티미디어 구동 환경에서도 동작합니다.

 

3.1. DllImport

먼저, 네이티브 함수 P/Invoke는 extern 메서드에만 적용할 수 있습니다. 이때 사용하는 특성은 DllImport입니다. 아래와 같이 사용하여 네이티브 함수를 C#에서 사용할 수 있습니다.

class NativeMethods
{
    [DllImport("user32.dll")]
    public extern static int PeekMessage(out MSG message, IntPtr hwnd, int min, int max, int pm);
}

 

위 예제에서는 Windows API 중 하나인 PeekMessage 함수를 P/Invoke를 사용하여 가져온 것을 보여주는 예제입니다. 첫 번째 인자로 반드시 사용할 라이브러리의 파일 이름을 작성해주어야 합니다. 확장자는 붙지 않아도 무관합니다.

 

DllImport의 필드 및 속성은 플랫폼에 따라 다르므로 자세히 나열하여 설명하지는 않겠습니다. 단, EntryPoint를 적용하면 정확한 네이티브 함수 이름을 적용할 수 있으며, CharSet을 적용하면 해당 네이티브 함수에서 사용하는 문자셋을 적용하여 문자열이 깨지지 않도록 도와줄 수 있습니다. 또한 해당 네이티브 함수의 함수 호출 방식에 따라 CallingConvention을 적용하면 C 함수 호출 방식과 표준 함수 호출 방식을 구분해줄 수 있습니다.

 

3.2. StructLayout

구조체 P/Invoke는 StructLayout 특성을 사용하여 처리합니다. 아래와 같이 사용하여 구조체를 네이티브에 맞게 변환할 수 있습니다.

[StructLayout(LayoutKind.Sequencial)]
public struct POINT
{
    public int x, y;
}

 

위 예제에서는 Windows API 중 하나인 POINT 구조체를 P/Invoke를 사용하여 정의한 것을 보여주는 예제입니다. 위 POINT 구조체는 필드가 순서에 맞게 배열됩니다.

 

LayoutKind에는 Sequencial, Explicit, Auto 세 가지의 값이 포함되며, Sequencial은 정의한 순서대로 필드가 배열되도록 하는 것이고, Explicit은 명시적으로 필드의 위치를 지정할 수 있도록 하는 것이며, 마지막으로 Auto는 런타임에서 알아서 배열하도록 하는 것입니다.

 

Explicit은 FieldOffset 특성과 같이 사용할 수 있습니다. FieldOffset은 필드에 사용하는 특성이며, 바이트 위치를 지정할 수 있습니다. 잘 사용하면 C언어의 union과 같이 구현할 수 있게 됩니다.

 

StructLayout의 필드 및 속성은 플랫폼에 따라 다르므로 자세히 나열하여 설명하지는 않겠습니다. 단, CharSet을 적용하면 네이티브 구조체에서 사용하는 문자셋을 적용하여 문자열이 깨지지 않도록 도와줄 수 있습니다. Size를 적용하면 구조체의 크기를 명시화할 수 있습니다.

 

참고로 StructLayout은 굳이 P/Invoke를 위해 사용할 필요는 없기 때문에 P/Invoke 특성이라고 하기에는 조금 부족하지만 주로 P/Invoke를 위해 사용하기에 작성했습니다.

 

3.3. MarshalAs

값에 대한 마샬링을 수행하기 위해서 MarshalAs 특성을 사용합니다. 보통 필드나 매개변수에 사용되는데, 자료의 형태를 구체화하기 위해 사용합니다.

 

배열 크기 명시화, 문자열 문자셋 지정, 문자열 형태 지정, 4바이트 Boolean 지정 등에 사용되며, UnmanagedType 열거형에 해당 타입에 대한 값이 명시되어 있습니다.

 

4. 특성 대상 구체화

특성을 적용할 대상을 구체화할 수도 있습니다. 예를 들어, 메서드에서 반환 값에 대한 특성을 적용한다던가 하는 식으로 말이죠.

 

아래 표는 Microsoft Developer Network(MSDN)에서 가져온 특성 대상 나열 표입니다.

 

선언 가능한 대상

assembly

assembly

module

module

class

type

struct

type

interface

type

enum

type

delegate

type, return

method

method, return

parameter

param

필드

field

property — indexer

property

property — get accessor

method, return

property — set accessor

method, param, return

event — field

event, field, method

event — property

event, property

event — add

method, param

event — remove

method, param

 

선언은 특성을 사용할 위치를 말하는 것으로, 선언에 해당하는 요소에 특성을 적용하면 해당 특성의 특성 대상 구체화가 가능한 대상을 말하는 것이 가능한 대상입니다.

 

특성 대상을 구체화 하려면 아래와 같이 사용하면 됩니다.

[Guid("12345678-1234-1234-1234-123456789abc"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface ISampleInterface
{
    [DispId(17)]  // set the DISPID of the method
    [return: MarshalAs(UnmanagedType.Interface)]  // set the marshaling on the return type
    object DoWork();
}

 

위 코드에서는 메서드에 특성을 사용하고, 해당 특성은 return에 대상을 구체화한다는 것으로, DoWork의 반환 값은 관리되지 않은 인터페이스로 마샬링 되게 됩니다.

0 Comments
댓글쓰기 폼