C # 제네릭 메서드 선택


9

C #에서 다른 차원의 기하학적 엔티티와 작동 할 수있는 일반 알고리즘을 작성하려고합니다.

내가 가지고있는 다음과 같은 인위적인 예에서 Point2Point3둘 다 간단한 구현 IPoint인터페이스를.

이제 함수 GenericAlgorithm를 호출하는 함수가 GetDim있습니다. 유형에 따라이 기능에 대한 여러 정의가 있습니다. 구현하는 모든 것에 대해 정의 된 폴백 함수도 있습니다 IPoint.

처음에 다음 프로그램의 출력은 2, 3이 될 것으로 예상했지만 0, 0입니다.

interface IPoint {
    public int NumDims { get; } 
}

public struct Point2 : IPoint {
    public int NumDims => 2;
}

public struct Point3 : IPoint {
    public int NumDims => 3;
}

class Program
{
    static int GetDim<T>(T point) where T: IPoint => 0;
    static int GetDim(Point2 point) => point.NumDims;
    static int GetDim(Point3 point) => point.NumDims;

    static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim(point);

    static void Main(string[] args)
    {
        Point2 p2;
        Point3 p3;
        int d1 = GenericAlgorithm(p2);
        int d2 = GenericAlgorithm(p3);
        Console.WriteLine("{0:d}", d1);        // returns 0 !!
        Console.WriteLine("{0:d}", d2);        // returns 0 !!
    }
}

어떤 이유로 콘크리트 유형 정보가에서 손실됩니다 GenericAlgorithm. 왜 이런 일이 일어나는지 완전히 이해하지 못하지만 괜찮습니다. 이 방법으로 할 수 없다면 다른 대안이 있습니까?


2
"대체 기능도 있습니다"정확히 이것의 목적은 무엇입니까? 인터페이스 구현의 요점은 NumDims속성을 사용할 수 있도록하는 것입니다. 어떤 경우에는 왜 그것을 무시합니까?
John Wu

기본적으로 컴파일됩니다. 처음에는 런타임에 JIT 컴파일러가 특수 구현을 찾을 수없는 경우 폴백 함수가 필요하다고 생각했습니다 GetDim(즉, 전달 Point4하지만 GetDim<Point4>존재하지 않습니다). 그러나 컴파일러가 특수 구현을 찾는 것을 귀찮게하지는 않습니다.
mohamedmoussa

1
@woggy : 여러분은 "컴파일러가 특수 구현을 찾는 것을 귀찮게하는 것 같지 않다"고 말합니다. 마치 이것이 디자이너와 구현 자의 측면에서 게으름의 문제인 것처럼 말입니다. 그렇지 않습니다. .NET에서 제네릭이 어떻게 표현되는지는 중요합니다. C ++의 템플릿과 같은 종류의 전문화가 아닙니다. 제네릭 메서드는 각 형식 인수에 대해 별도로 컴파일되지 않으며 한 번 컴파일됩니다. 확실히 이것의 장단점이 있지만, "형질"의 문제는 아닙니다.
Jon Skeet

@jonskeet 죄송합니다. 언어 선택이 형편 없다면 여기에 제가 고려하지 않은 복잡성이있을 것입니다. 내 이해는 컴파일러가 참조 유형에 대해 별도의 함수를 컴파일하지는 않지만 값 유형 / 구조체에 대해서는 정확합니까?
mohamedmoussa

@ woggy : 그것은 C # 컴파일러와는 완전히 별개의 문제인 JIT 컴파일러이며, 과부하 해결을 수행하는 C # 컴파일러입니다. 일반 방법에 대한 IL은 전문화 당 한 번이 아니라 한 번만 생성됩니다.
Jon Skeet

답변:


10

이 방법:

static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim(point);

... 항상 전화 할 것 GetDim<T>(T point)입니다. 과부하 해결은 컴파일 타임에 수행되며 그 단계에는 적용 가능한 다른 방법이 없습니다.

실행 시간에 과부하 해결을 호출 하려면 동적 입력을 사용해야합니다 (예 :

static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim((dynamic) point);

그러나 일반적으로 상속을 사용하는 것이 좋습니다. 예를 들어 단일 메서드를 사용하고 반환 할 수 있습니다 point.NumDims. 실제 코드에는 그와 동등한 것이 까다로운 몇 가지 이유가 있다고 가정하지만 더 많은 컨텍스트가 없으면 상속을 사용하여 전문화를 수행하는 방법에 대해 조언 할 수 없습니다. 그러나 옵션은 다음과 같습니다.

  • 대상의 실행 시간 유형에 따른 전문화를위한 상속 (권장)
  • 실행 시간 과부하 해결을위한 동적 타이핑

실제 상황은 내가이있다 AxisAlignedBoundingBox2하고 AxisAlignedBoundingBox3. 나는이 Contains여부를 결정하는 데 사용되는 정적 방법 수집 상자가 포함 Line2또는 Line3(하나는 상자의 종류에 의존하는)을. 두 유형 사이의 알고리즘 논리는 차원 수가 다른 것을 제외하고는 정확히 동일합니다. Intersect내부적으로 올바른 유형에 특화된 호출이 있습니다 . 가상 함수 호출 / 동적을 피하고 싶습니다. 제네릭을 사용하는 이유는 ... 물론 코드를 복사 / 붙여 넣기하고 계속 진행할 수 있습니다.
mohamedmoussa

1
@ woggy : 단지 설명에서 그것을 시각화하는 것은 매우 어렵습니다. 상속을 사용 하여이 작업을 수행하는 데 도움이 필요한 경우 최소한의 완전한 예를 사용하여 새 질문을 작성하는 것이 좋습니다.
Jon Skeet

좋아, 할 것이다, 나는 좋은 예를 제시하지 않은 것처럼이 대답을 지금 받아 들일 것이다.
mohamedmoussa

6

C # 8.0부터는 일반적인 방법을 사용하지 않고 인터페이스에 기본 구현을 제공 할 수 있어야합니다.

interface IPoint {
    int NumDims { get => 0; }
}

일반적인 방법과 IPoint구현 당 과부하를 구현하면 Liskov 대체 원칙 (SOLID의 L)에도 위배됩니다. 당신은 밀어 좋을 것 알고리즘을 각에 IPoint있는 수단이 하나의 메소드 호출하기 만합니다 구현 :

static int GetDim(IPoint point) => point.NumDims;

3

방문자 패턴

사용의 대안 으로 아래와 같이 방문자 패턴dynamic 을 사용할 수 있습니다.

interface IPoint
{
    public int NumDims { get; }
    public int Accept(IVisitor visitor);
}

public struct Point2 : IPoint
{
    public int NumDims => 2;

    public int Accept(IVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public struct Point3 : IPoint
{
    public int NumDims => 3;

    public int Accept(IVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public class Visitor : IVisitor
{
    public int Visit(Point2 toVisit)
    {
        return toVisit.NumDims;
    }

    public int Visit(Point3 toVisit)
    {
        return toVisit.NumDims;
    }
}

public interface IVisitor<T>
{
    int Visit(T toVisit);
}

public interface IVisitor : IVisitor<Point2>, IVisitor<Point3> { }

class Program
{
    static int GetDim<T>(T point) where T : IPoint => 0;
    static int GetDim(Point2 point) => point.NumDims;
    static int GetDim(Point3 point) => point.NumDims;

    static int GenericAlgorithm<T>(T point) where T : IPoint => point.Accept(new Visitor());

    static void Main(string[] args)
    {
        Point2 p2;
        Point3 p3;
        int d1 = GenericAlgorithm(p2);
        int d2 = GenericAlgorithm(p3);
        Console.WriteLine("{0:d}", d1);        // returns 2
        Console.WriteLine("{0:d}", d2);        // returns 3
    }
}

1

왜 클래스와 인터페이스에서 GetDim 함수를 정의하지 않겠습니까? 실제로, GetDim 함수를 정의 할 필요는 없으며 NumDims 속성 만 사용하면됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.