Próbuję pisać ogólne algorytmy w języku C #, które mogą pracować z elementami geometrycznymi o innym wymiarze.
W poniższym wymyślonym przykładzie mam Point2
i Point3
oba, implementujące prosty IPoint
interfejs.
Teraz mam funkcję, GenericAlgorithm
która wywołuje funkcję GetDim
. Istnieje wiele definicji tej funkcji w zależności od typu. Istnieje również funkcja rezerwowa zdefiniowana dla wszystkiego, co implementuje IPoint
.
Początkowo spodziewałem się, że wynikiem następującego programu będzie 2, 3. Jest to jednak 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 !!
}
}
OK, więc z jakiegoś powodu zaginęła konkretna informacja o typie GenericAlgorithm
. Nie do końca rozumiem, dlaczego tak się dzieje, ale dobrze. Jeśli nie mogę tego zrobić w ten sposób, jakie mam inne alternatywy?
NumDims
właściwość jest dostępna. Dlaczego w niektórych przypadkach ignorujesz to?GetDim
(tzn. Przekazuję,Point4
aleGetDim<Point4>
nie istnieje). Wydaje się jednak, że kompilator nie chce szukać specjalnej implementacji.Odpowiedzi:
Ta metoda:
... zawsze zadzwoni
GetDim<T>(T point)
. Rozdzielczość przeciążenia jest wykonywana w czasie kompilacji i na tym etapie nie ma innej stosownej metody.Jeśli chcesz, aby w czasie wykonywania wywoływana była rozdzielczość przeciążenia , musisz użyć dynamicznego pisania, np
Ale generalnie lepszym pomysłem jest użycie do tego dziedziczenia - w twoim przykładzie oczywiście możesz mieć tylko jedną metodę i zwrócić
point.NumDims
. Zakładam, że w twoim prawdziwym kodzie istnieje jakiś powód, dla którego ekwiwalent jest trudniejszy, ale bez większego kontekstu nie możemy doradzić, jak użyć dziedziczenia do wykonania specjalizacji. Są to jednak twoje opcje:źródło
AxisAlignedBoundingBox2
iAxisAlignedBoundingBox3
. MamContains
metodę statyczną, która jest używana do ustalenia, czy zbiór pudełek zawiera znakLine2
lubLine3
(który zależy od rodzaju pudełek). Logika algorytmu między tymi dwoma typami jest dokładnie taka sama, z tym wyjątkiem, że liczba wymiarów jest inna. Istnieją również połączeniaIntersect
wewnętrzne, które muszą być wyspecjalizowane dla właściwego typu. Chcę uniknąć wywoływania funkcji wirtualnych / dynamicznych, dlatego używam ogólnych ... oczywiście, mogę po prostu skopiować / wkleić kod i przejść dalej.Począwszy od wersji C # 8.0 powinieneś być w stanie zapewnić domyślną implementację interfejsu, zamiast wymagać ogólnej metody.
Wdrożenie ogólnej metody i przeciążenia na
IPoint
wdrożenie również narusza zasadę podstawienia Liskowa (L w SOLID). Lepiej byłoby wcisnąć algorytm do każdejIPoint
implementacji, co oznacza, że potrzebujesz tylko jednego wywołania metody:źródło
Wzór gościa
jako alternatywę dla
dynamic
użycia możesz użyć wzorca gościa, jak poniżej:źródło
Dlaczego nie zdefiniujesz funkcji GetDim w klasie i interfejsie? W rzeczywistości nie musisz definiować funkcji GetDim, po prostu użyj właściwości NumDims.
źródło