2016년 2월 25일 목요일

C# 프로그래밍 가이드 중 일부 발췌 - 제네릭(Generic), 인덱서(Indexer), 인터페이스(Interface)

개인적인 copy & paste 정리입니다. 글을 보시기 보다는 아래 링크에 방문하세요.

https://msdn.microsoft.com/ko-kr/library/67ef8sbd.aspx


제네릭

  • 필수적 단일 문자 이름으로도 자체 설명이 가능하여 설명적인 이름을 굳이 사용할 필요가 없는 경우가 아니면 제네릭 형식 매개 변수 이름을 설명적인 이름으로 지정하십시오.
    public interface ISessionChannel<TSession> { /*...*/ }
    public delegate TOutput Converter<TInput, TOutput>(TInput from);
    public class List<T> { /*...*/ }
    
    
    
  • 선택적 단일 문자 형식 매개 변수를 사용하는 형식에는 형식 매개 변수 이름으로 T를 사용하십시오.
    public int IComparer<T>() { return 0; }
    public delegate bool Predicate<T>(T item);
    public struct Nullable<T> where T : struct { /*...*/ }
    
    
    
  • 필수적 설명적인 형식 매개 변수 이름 앞에 “T”를 붙이십시오.
    public interface ISessionChannel<TSession>
    {
        TSession Session { get; }
    }
    
    
    
  • 선택적 매개 변수 이름 안에서 형식 매개 변수에 적용되는 제약 조건을 나타내십시오. 예를 들어 ISession으로 제한되는 매개 변수의 이름은TSession이 될 수 있습니다.
대리자는 자체 형식 매개 변수를 정의할 수 있습니다. 제네릭 대리자를 참조하는 코드에서는 제네릭 클래스를 인스턴스화하거나 제네릭 메서드를 호출할 때와 마찬가지로 다음 예제와 같이 형식 매개 변수를 지정하여 폐쇄형 구성 형식을 만들 수 있습니다.
public delegate void Del<T>(T item);
public static void Notify(int i) { }

Del<int> m1 = new Del<int>(Notify);


C# 버전 2.0에는 메서드 그룹 변환이라는 새로운 기능이 있습니다. 이 기능은 제네릭 대리자뿐만 아니라 구체적인 대리자에도 적용되며, 이를 통해 위 코드를 다음과 같이 단순화된 구문으로 작성할 수 있습니다.
Del<int> m2 = Notify;

제네릭 클래스 및 메서드에서 다음과 같은 내용을 미리 알지 못하는 경우 매개 변수화된 형식 T에 기본값을 할당하는 방법에 대한 문제가 발생합니다.
  • T가 참조 형식인지 값 형식인지 여부
  • T가 값 형식인 경우 숫자 값인지 구조체인지 여부
t가 매개 변수화된 형식 T의 변수인 경우 "t = null"과 같은 구문은 T가 참조 형식인 경우에만 유효하고 "t = 0"은 구조체가 아닌 숫자 값 형식인 경우에만 사용할 수 있습니다. 참조 형식에 대해서는 null을 반환하고 숫자 값 형식에는 0을 반환하는 default 키워드를 사용하면 이 문제를 해결할 수 있습니다. 구조체에 대해서는 멤버가 값 형식인지 참조 형식인지 여부에 따라 0 또는 null로 초기화된 구조체의 각 멤버가 반환됩니다. null 허용 값 형식의 경우 default는 다른 구조체와 같이 초기화되는System.Nullable<T>을 반환합니다.
예제가 좀 이상한데..

https://msdn.microsoft.com/ko-kr/library/c6cyy67b.aspx

다음은 C# 제네릭과 C++ 템플릿 간의 주요 차이점입니다.
  • C# 제네릭은 C++ 템플릿과 같은 정도의 융통성을 제공하지 않습니다. 예를 들어, C# 제네릭 클래스에서 사용자 정의 연산자를 호출할 수는 있지만 산술 연산자를 호출할 수는 없습니다.
  • C#에서는 template C<int i> {} 같은 비형식 템플릿 매개 변수를 허용하지 않습니다.
  • C#에서는 명시적 특수화를 지원하지 않습니다. 즉, 특정 형식에 대한 템플릿을 사용자 지정하여 구현할 수 없습니다.
  • C#에서는 부분 특수화를 지원하지 않습니다. 즉, 형식 인수의 하위 집합을 사용자 지정하여 구현할 수 없습니다.
  • C#에서는 형식 매개 변수를 제네릭 형식에 대한 기본 클래스로 사용할 수 없습니다.
  • C#에서는 형식 매개 변수에 기본 형식을 사용할 수 없습니다.
  • C#에서는 생성된 형식을 제네릭으로 사용할 수는 있지만 제네릭 형식 매개 변수 자체가 제네릭이 될 수는 없습니다.C++에서는 템플릿 매개 변수를 허용합니다.
  • C++에서는 템플릿의 모든 형식 매개 변수에 대해서는 유효하지 않을 수 있는 코드가 허용되며 이러한 코드는 형식 매개 변수로 사용되는 특정 형식에 대해 검사를 받습니다. C#에서는 제약 조건을 충족하는 모든 형식과 함께 사용할 수 있도록 클래스의 코드를 작성해야 합니다. 예를 들어, C++의 경우 형식 매개 변수의 개체에 대해 +  - 산술 연산자를 사용하는 함수를 작성할 수 있습니다. 이 경우 이러한 연산자를 지원하지 않는 형식으로 템플릿을 인스턴스화할 때 오류가 발생합니다. C#에서는 이러한 함수를 작성할 수 없고 제약 조건에서 추론 가능한 언어 구문만 사용할 수 있습니다.

https://msdn.microsoft.com/ko-kr/library/f4a6ta2h.aspx

제네릭 형식 또는 메서드가 MSIL(Microsoft Intermediate Language)로 컴파일되면 자체적으로 형식 매개 변수를 갖는 것으로 식별하는 메타데이터가 포함됩니다. 제네릭 형식의 MSIL이 사용되는 방식은 제공된 형식 매개 변수가 값 형식인지 참조 형식인지에 따라 다릅니다.
값 형식을 매개 변수로 사용하여 제네릭 형식을 처음 생성할 경우 런타임에서는 제공된 매개 변수를 MSIL의 해당 위치에 대체하여 특수화된 제네릭 형식을 만듭니다. 고유한 값 형식이 매개 변수로 사용될 때마다 특수화된 제네릭 형식이 만들어집니다.

참조 형식의 수는 프로그램마다 크게 다를 수 있으므로, 제네릭을 C# 방식으로 구현하면 컴파일러가 참조 형식의 제네릭 클래스에 대해 만드는 특수화된 클래스의 수가 1개로 줄어들어 코드가 매우 간결해집니다.
뿐만 아니라, 값 형식 또는 참조 형식 매개 변수를 사용하여 제네릭 C# 클래스가 인스턴스화되면 런타임에 리플렉션을 통해 쿼리할 수 있고 실제 형식과 형식 매개 변수를 모두 조사할 수 있습니다.
 value type은 형식에 따라서 인스턴스 생성 후 재사용 reference type은 각각 인스턴스 생성

https://msdn.microsoft.com/ko-kr/library/ac30fak4.aspx

.NET Framework 클래스 라이브러리 버전 2.0에서는 System.Collections.Generic이라는 새로운 네임스페이스를 제공하며, 이 네임스페이스에는 바로 사용할 수 있는 제네릭 컬렉션 클래스 및 관련 인터페이스가 있습니다. System 같은 기타 네임스페이스에서도 IComparable<T> 같은 새로운 제네릭 인터페이스를 제공합니다. 이러한 클래스 및 인터페이스는 이전 버전의 .NET Framework에서 제공하던 제네릭이 아닌 컬렉션 클래스보다 효율적이고 형식 안전적입니다. 사용자 지정 컬렉션 클래스를 디자인하고 구현하기 전에 .NET Framework 클래스 라이브러리에서 제공하는 클래스를 사용하거나 파생시킬 수 있는지 고려해 보십시오.

인덱서

https://msdn.microsoft.com/ko-kr/library/6x16t2tx.aspx

class SampleCollection<T>
{
    // Declare an array to store the data elements.
    private T[] arr = new T[100];

    // Define the indexer, which will allow client code
    // to use [] notation on the class instance itself.
    // (See line 2 of code in Main below.)        
    public T this[int i]
    {
        get
        {
            // This indexer is very simple, and just returns or sets
            // the corresponding element from the internal array.
            return arr[i];
        }
        set
        {
            arr[i] = value;
        }
    }
}

// This class shows how client code uses the indexer.
class Program
{
    static void Main(string[] args)
    {
        // Declare an instance of the SampleCollection type.
        SampleCollection<string> stringCollection = new SampleCollection<string>();

        // Use [] notation on the type.
        stringCollection[0] = "Hello, World";
        System.Console.WriteLine(stringCollection[0]);
    }
}
// Output:
// Hello, World.
https://msdn.microsoft.com/ko-KR/library/2549tw02.aspx

클래스나 구조체에 대한 인덱서를 선언하려면 다음 예제에서와 같이 this 키워드를 사용합니다.
public int this[int index]    // Indexer declaration
{
    // get and set accessors
}

인덱서 형식과 해당 매개 변수 형식에는 적어도 인덱서 자체와 동등한 수준으로 액세스할 수 있어야 합니다.액세스 가능성 수준에 대한 자세한 내용은 액세스 한정자를 참조하십시오.
인터페이스와 함께 인덱서를 사용하는 방법에 대한 자세한 내용은 인터페이스 인덱서를 참조하십시오.
인덱서 시그니처는 인덱서 정식 매개 변수의 수 및 형식으로 구성됩니다.이 시그니처는 인덱서 형식 또는 정식 매개 변수 이름을 포함하지 않습니다.같은 클래스에 두 개 이상의 인덱서를 선언한 경우, 모든 인덱서가 다른 시그니처를 가져야 합니다.
인덱서 값은 변수로 분류되지 않기 때문에 인덱서 값을 ref 또는 out 매개 변수로 전달할 수 없습니다.
다른 언어에서 사용할 수 있는 이름을 인덱서에 부여하려면 선언에 name 특성을 사용합니다.예를 들면 다음과 같습니다.
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this [int index]   // Indexer declaration
{
}
이 인덱서의 이름은 TheItem입니다.name 특성을 제공하지 않으면 기본 이름으로 Item이 사용됩니다.
이 예제에서는 요일을 저장하는 클래스를 선언합니다.문자열과 요일 이름을 가져오고 상응하는 정수를 반환하는 get 접근자를 선언합니다.예를 들어, 일요일은 0을 반환하고 월요일은 1을 반환하는 방식입니다.

// Using a string as an indexer value
class DayCollection
{
    string[] days = { "Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat" };

    // This method finds the day or returns -1
    private int GetDay(string testDay)
    {

        for (int j = 0; j < days.Length; j++)
        {
            if (days[j] == testDay)
            {
                return j;
            }
        }

        throw new System.ArgumentOutOfRangeException(testDay, "testDay must be in the form \"Sun\", \"Mon\", etc");
    }

    // The get accessor returns an integer for a given string
    public int this[string day]
    {
        get
        {
            return (GetDay(day));
        }
    }
}
https://msdn.microsoft.com/ko-kr/library/tkyhsw31.aspx

interface(C# 참조) 에서 인덱서를 선언할 수 있습니다. 인터페이스 인덱서의 접근자와 클래스 인덱서의 접근자는 다음과 같은 차이점이 있습니다.
  • 인터페이스 접근자는 한정자를 사용하지 않습니다.
  • 인터페이스 접근자에는 본문이 없습니다.
따라서 접근자의 목적은 인덱서가 읽기/쓰기, 읽기 전용 또는 쓰기 전용인지 여부를 나타내는 것입니다.
다음은 인터페이스 인덱서 접근자의 예제입니다.
public interface ISomeInterface
{
    //...

    // Indexer declaration:
    string this[int index]
    {
        get;
        set;
    }
}

https://msdn.microsoft.com/ko-kr/library/4bsztef7.aspx

인덱서는 속성과 비슷합니다. 다음 표에 나와 있는 차이점을 제외하면 접근자에 정의된 모든 규칙이 인덱서 접근자에도 적용됩니다.
Property
인덱서
공용 데이터 멤버인 것처럼 메서드를 호출할 수 있습니다.
개체 자체의 배열 표기법을 사용하여 개체의 내부 컬렉션 요소에 액세스할 수 있습니다.
단순한 이름을 통해 액세스할 수 있습니다.
인덱스를 통해 액세스할 수 있습니다.
정적 또는 인스턴스 멤버가 될 수 있습니다.
인스턴스 멤버여야 합니다.
속성의 get 접근자에는 매개 변수가 없습니다.
인덱서의 get 접근자는 인덱서와 동일한 정식 매개 변수 목록을 가집니다.
속성의 set 접근자에는 명시적인 value 매개 변수가 포함됩니다.
인덱서의 set 접근자는 value 매개 변수 외에도 인덱서와 동일한 정식 매개 변수 목록을 가집니다.
자동으로 구현된 속성(C# 프로그래밍 가이드) 이 있는 약식 구문을 지원합니다.
약식 구문을 지원하지 않습니다.

인터페이스

https://msdn.microsoft.com/ko-KR/library/ms173156.aspx

 C# 언어는 클래스에 대한 다중 상속을 지원하지 않기 때문에, 해당 기능이 중요합니다. 또한 구조체는 다른 구조체나 클래스에서 실제로 상속할 수 없기 때문에 구조체에 대한 상속을 시뮬레이트하려는 경우 인터페이스를 사용해야 합니다.
다음 예제에서는 인터페이스 키워드를 사용하여 인터페이스를 정의하는 방법을 보여줍니다.
interface IEquatable<T>
{
    bool Equals(T obj);
}
인터페이스는 다른 인터페이스를 구현할 수 있습니다.클래스는 상속하는 기본 클래스 또는 다른 인터페이스에서 구현하는 인터페이스를 통해 인터페이스를 여러 번 포함할 수 있습니다.그러나 클래스는 인터페이스의 구현을 한 번만 제공할 수 있으며 클래스가 인터페이스를 클래스 정의의 일부로 선언하는 경우에만 제공할 수 있습니다(class ClassName : InterfaceName).인터페이스를 구현하는 기본 클래스를 상속했기 때문에 인터페이스가 상속되는 경우 기본 클래스는 인터페이스 멤버의 구현을 제공합니다.그러나 파생 클래스는 상속된 구현을 사용하는 대신 인터페이스 멤버를 다시 구현할 수 있습니다.

인터페이스에는 다음과 같은 속성이 있습니다.
  • 인터페이스는 추상 기본 클래스와 같습니다. 인터페이스를 구현하는 모든 클래스 또는 구조체는 모든 멤버를 구현해야 합니다.
  • 인터페이스는 직접 인스턴스화할 수 없습니다.해당 멤버는 인터페이스를 구현하는 클래스 또는 구조체에 의해 구현됩니다.
  • 인터페이스는 이벤트, 인덱서, 메서드 및 속성을 포함할 수 있습니다.
  • 인터페이스에는 메서드의 구현이 포함되지 않습니다.
  • 클래스 또는 구조체는 여러 인터페이스를 구현할 수 있습니다.클래스는 기본 클래스를 상속할 수 있으며 하나 이상의 인터페이스를 제공할 수도 있습니다.
https://msdn.microsoft.com/ko-KR/library/ms173157.aspx

클래스에서 시그니처가 동일한 멤버가 들어 있는 두 인터페이스를 구현하는 경우 클래스에서 이 멤버를 구현하면 두 인터페이스 모두에서 이 멤버를 해당 구현으로 사용하게 됩니다.

그러나 두 인터페이스 멤버가 동일한 기능을 수행하지 않는 경우에는 인터페이스 하나 또는 모두의 구현이 잘못될 수 있습니다.인터페이스를 통해서만 호출되고 특정 인터페이스에만 관련되는 클래스 멤버를 만드는 방식으로 인터페이스 멤버를 명시적으로 구현할 수 있습니다. 이렇게 하려면 인터페이스 이름과 마침표를 사용하여 클래스 멤버의 이름을 지정해야 합니다. 예를 들면 다음과 같습니다.
public class SampleClass : IControl, ISurface
{
    void IControl.Paint()
    {
        System.Console.WriteLine("IControl.Paint");
    }
    void ISurface.Paint()
    {
        System.Console.WriteLine("ISurface.Paint");
    }
}

댓글 없음:

댓글 쓰기