C #에서 템플릿 전문화를 수행하는 방법


84

C # 전문화는 어떻게 하시겠습니까?

문제를 제기하겠습니다. 템플릿 유형이 있지만 그것이 무엇인지 모릅니다. 그러나 XYZ호출하려는 것에서 파생 된 것인지 알고 있습니다 .alternativeFunc(). 가장 좋은 방법은 전문 함수 나 클래스를 호출하는 것입니다 normalCall반환 .normalFunc()의 파생 유형에 다른 전문성을 가지고있는 동안 XYZ전화로 .alternativeFunc(). 이것이 C #에서 어떻게 수행됩니까?

답변:


86

C #에서 전문화에 가장 가까운 방법은보다 구체적인 오버로드를 사용하는 것입니다. 그러나 이것은 깨지기 쉬우 며 가능한 모든 사용을 포함하지는 않습니다. 예를 들면 :

void Foo<T>(T value) {Console.WriteLine("General method");}
void Foo(Bar value) {Console.WriteLine("Specialized method");}

여기서 컴파일러가 컴파일 할 때 유형을 알고 있으면 가장 구체적인 유형을 선택합니다.

Bar bar = new Bar();
Foo(bar); // uses the specialized method

하나....

void Test<TSomething>(TSomething value) {
    Foo(value);
}

이것은 컴파일 타임에 레코딩되므로 Foo<T>에도 사용 TSomething=Bar됩니다.

한 가지 다른 접근 방식은 제네릭 메서드 에서 형식 테스트를 사용 하는 것입니다. 그러나 이것은 일반적으로 좋지 않은 생각이며 권장되지 않습니다.

기본적으로 C #은 다형성을 제외하고는 전문화 작업을 원하지 않습니다.

class SomeBase { public virtual void Foo() {...}}
class Bar : SomeBase { public override void Foo() {...}}

여기에서는 Bar.Foo항상 올바른 재정의로 해결됩니다.


63

C ++ 템플릿으로 수행 할 수있는 템플릿 전문화에 대해 이야기하고 있다고 가정합니다. 이와 같은 기능은 실제로 C #에서 사용할 수 없습니다. 이는 C # 제네릭이 컴파일 중에 처리되지 않고 런타임의 기능이기 때문입니다.

그러나 C # 3.0 확장 메서드를 사용하여 유사한 효과를 얻을 수 있습니다. 다음은 MyClass<int>템플릿 전문화와 마찬가지로 유형에 대해서만 확장 메서드를 추가하는 방법을 보여주는 예제입니다. 그러나 C # 컴파일러는 항상 확장 메서드보다 표준 메서드를 선호하므로이를 사용하여 메서드의 기본 구현을 숨길 수는 없습니다.

class MyClass<T> {
  public int Foo { get { return 10; } }
}
static class MyClassSpecialization {
  public static int Bar(this MyClass<int> cls) {
    return cls.Foo + 20;
  }
}

이제 다음과 같이 작성할 수 있습니다.

var cls = new MyClass<int>();
cls.Bar();

전문화가 제공되지 않을 때 사용되는 메서드에 대한 기본 사례를 갖고 싶다면 하나의 일반 Bar확장 메서드를 작성 하는 것이 트릭을 수행해야한다고 생각합니다.

  public static int Bar<T>(this MyClass<T> cls) {
    return cls.Foo + 42;
  }

Foo property vs Bar 방법 ...별로 일반적인 전문화가 아닌 것 같습니다 ...
Marc Gravell

2
아니요, 일반적인 사양은 아니지만 할 수있는 유일한 간단한 작업입니다 ... (AFAIK)
Tomas Petricek

3
이것은 확장 static메서드를 사용하지 않아도 잘 작동하는 것으로 보입니다 . 제네릭 형식을 취하는 메서드 만 있습니다. 즉, @MarcGravell 답변에서 지적한 문제 는 메서드를 특정 "데이터"유형 ( / )으로 템플릿 화하는 대신 MyClass<T>/ 와 같은 인수를 기반으로 "템플릿"하는 방식으로 회피되는 것으로 보입니다 . MyClass<int>Tint
Slipp D. Thompson 2014-04-18

4
추가 제한 사항은 메서드 내에서와 같은 간접 일반 호출에 대해 작동하지 않는다는 것입니다 void CallAppropriateBar<T>() { (new MyClass<T>()).Bar(); }.
BartoszKP

18

중간 클래스와 사전을 추가하여 전문화가 가능합니다 .

T를 전문화하기 위해 (예) Apply라는 메서드가있는 일반 인터페이스를 만듭니다. 인터페이스가 구현되는 특정 클래스의 경우 해당 클래스에 특정한 Apply 메서드를 정의합니다. 이 중간 클래스를 특성 클래스라고합니다.

해당 특성 클래스는 제네릭 메서드 호출에서 매개 변수로 지정 될 수 있으며, 그러면 (물론) 항상 올바른 구현을 취합니다.

수동으로 지정하는 대신 traits 클래스를 global에 저장할 수도 있습니다 IDictionary<System.Type, object>. 그런 다음 찾아 볼 수 있고 짜잔, 당신은 거기에 진정한 전문화가 있습니다.

편리한 경우 확장 메서드에 노출 할 수 있습니다.

class MyClass<T>
{
    public string Foo() { return "MyClass"; }
}

interface BaseTraits<T>
{
    string Apply(T cls);
}

class IntTraits : BaseTraits<MyClass<int>>
{
    public string Apply(MyClass<int> cls)
    {
        return cls.Foo() + " i";
    }
}

class DoubleTraits : BaseTraits<MyClass<double>>
{
    public string Apply(MyClass<double> cls)
    {
        return cls.Foo() + " d";
    }
}

// Somewhere in a (static) class:
public static IDictionary<Type, object> register;
register = new Dictionary<Type, object>();
register[typeof(MyClass<int>)] = new IntTraits();
register[typeof(MyClass<double>)] = new DoubleTraits();

public static string Bar<T>(this T obj)
{
    BaseTraits<T> traits = register[typeof(T)] as BaseTraits<T>;
    return traits.Apply(obj);
}

var cls1 = new MyClass<int>();
var cls2 = new MyClass<double>();

string id = cls1.Bar();
string dd = cls2.Bar();

자세한 설명과 샘플은 내 최근 블로그 링크 와 후속 조치를 참조하십시오 .


이것은 팩토리 패턴이며 제네릭의 일부 단점 을 처리하는 적절한 방법 입니다
Yaur

1
@Yaur 나는 나에게 교과서 데코레이터 패턴처럼 보입니다.
Slipp D. Thompson 2014-04-18

13

템플릿 전문화를 시뮬레이션하는 패턴도 찾고있었습니다. 일부 상황에서 작동 할 수있는 몇 가지 접근 방식이 있습니다. 그러나 사건은 어떻습니까

static void Add<T>(T value1, T value2)
{
    //add the 2 numeric values
}

예를 들어 문을 사용하여 작업을 선택할 수 있습니다 if (typeof(T) == typeof(int)). 그러나 단일 가상 함수 호출의 오버 헤드로 실제 템플릿 전문화를 시뮬레이션하는 더 좋은 방법이 있습니다.

public interface IMath<T>
{
    T Add(T value1, T value2);
}

public class Math<T> : IMath<T>
{
    public static readonly IMath<T> P = Math.P as IMath<T> ?? new Math<T>();

    //default implementation
    T IMath<T>.Add(T value1, T value2)
    {
        throw new NotSupportedException();    
    }
}

class Math : IMath<int>, IMath<double>
{
    public static Math P = new Math();

    //specialized for int
    int IMath<int>.Add(int value1, int value2)
    {
        return value1 + value2;
    }

    //specialized for double
    double IMath<double>.Add(double value1, double value2)
    {
        return value1 + value2;
    }
}

이제 유형을 미리 알지 않고도 작성할 수 있습니다.

static T Add<T>(T value1, T value2)
{
    return Math<T>.P.Add(value1, value2);
}

private static void Main(string[] args)
{
    var result1 = Add(1, 2);
    var result2 = Add(1.5, 2.5);

    return;
}

구현 된 유형뿐만 아니라 파생 된 유형에 대해 특수화를 호출해야하는 경우 In인터페이스에 대한 매개 변수를 사용할 수 있습니다 . 그러나이 경우 메서드의 반환 유형은 T더 이상 제네릭 유형이 될 수 없습니다 .


정말 놀랍습니다. 감사합니다. 이를 통해 특정 유형에 대해 각각 작성된 기존 메소드를 호출하기위한 일반 인터페이스를 생성 할 수있었습니다. 이는 일반적으로 다시 작성할 수 없거나 적어도 큰 어려움이 있습니다. 끔찍한 일 if (type == typeof(int))을 한 다음 추가 복싱 / 언 박싱이 포함 된 일반 유형으로 되돌려 야하는 것처럼 보이기 시작했습니다 return (T)(object)result;(유형은 정적으로 알려지지 않고 논리적으로 만 알려져 있기 때문입니다)
Andrew Wright

6

제안 된 답변 중 일부는 런타임 유형 정보를 사용하고 있습니다. 본질적으로 컴파일 타임 바운드 메서드 호출보다 느립니다.

컴파일러는 C ++ 에서처럼 특수화를 적용하지 않습니다.

C ++와 유사한 효과를 내기 위해 일반적인 컴파일러가 완료된 후 코드를 삽입하는 방법으로 PostSharp를 살펴 보는 것이 좋습니다.


4

동적 해상도를 사용하여 .NET 4+로이를 달성하는 방법이 있다고 생각합니다.

static class Converter<T>
{
    public static string Convert(T data)
    {
        return Convert((dynamic)data);
    }

    private static string Convert(Int16 data) => $"Int16 {data}";
    private static string Convert(UInt16 data) => $"UInt16 {data}";
    private static string Convert(Int32 data) => $"Int32 {data}";
    private static string Convert(UInt32 data) => $"UInt32 {data}";
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Converter<Int16>.Convert(-1));
        Console.WriteLine(Converter<UInt16>.Convert(1));
        Console.WriteLine(Converter<Int32>.Convert(-1));
        Console.WriteLine(Converter<UInt32>.Convert(1));
    }
}

산출:

Int16 -1
UInt16 1
Int32 -1
UInt32 1

다른 유형에 대해 다른 구현이 호출됨을 보여줍니다.


2
조금 울고 싶어요.
user2864740

0

유형이 XYZ에서 파생되었는지 테스트하려는 경우 다음을 사용할 수 있습니다.

theunknownobject.GetType().IsAssignableFrom(typeof(XYZ));

그렇다면 "theunknownobject"를 XYZ로 캐스트하고 다음과 같이 alternateFunc ()를 호출 할 수 있습니다.

XYZ xyzObject = (XYZ)theunknownobject; 
xyzObject.alternativeFunc();

도움이 되었기를 바랍니다.


1
나는 C #을 많이 알지 못하지만 누가 당신에게 투표했는지 그 이유를 말해야합니다. 나는 당신의 대답이 잘못되었거나 무엇이 잘못되었는지 전혀 모릅니다.

확실하지 않습니다. 나에게는 충분히 유효한 것 같습니다. 필요 이상으로 좀 더 장황하지만.
jalf

3
내가 아니었지만 대답이 질문과 전혀 무관하기 때문이다. 조회"c++ template specialization"
georgiosd

이것은 항상 작동하지 않습니다. 예를 들어 T가 부울인지 확인한 다음 부울로 캐스팅 할 수 없습니다.
Kos
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.