Я пытаюсь написать универсальные алгоритмы на C #, которые могут работать с геометрическими объектами различной размерности.
В следующем надуманном примере я Point2
и Point3
оба реализую простой 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
. Я не совсем понимаю, почему это происходит, но хорошо. Если я не могу сделать это таким образом, какие у меня есть альтернативы?
NumDims
свойство доступно. Почему вы игнорируете это в некоторых случаях?GetDim
(т.е. я передаю,Point4
ноGetDim<Point4>
не существует). Однако, похоже, компилятору не стоит искать специализированную реализацию.Ответы:
Этот способ:
... всегда буду звонить
GetDim<T>(T point)
. Разрешение перегрузки выполняется во время компиляции , и на этом этапе нет другого применимого метода.Если вы хотите, чтобы разрешение перегрузки вызывалось во время выполнения , вам нужно использовать динамическую типизацию, например
Но обычно для этого лучше использовать наследование - в вашем примере, очевидно, вы могли бы просто иметь один метод и возвращать
point.NumDims
. Я предполагаю, что в вашем реальном коде есть какая-то причина, по которой этот эквивалент сложнее сделать, но без большего контекста мы не можем посоветовать, как использовать наследование для выполнения специализации. Это ваши варианты, хотя:источник
AxisAlignedBoundingBox2
иAxisAlignedBoundingBox3
. У меня естьContains
статический метод, который используется для определения, содержит ли набор ящиковLine2
илиLine3
(который зависит от типа ящиков). Логика алгоритма между двумя типами абсолютно одинакова, за исключением того, что количество измерений различно. Есть также вызовыIntersect
внутри, которые должны быть специализированы для правильного типа. Я хочу избежать виртуальных вызовов функций / динамических, поэтому я использую дженерики ... конечно, я могу просто скопировать / вставить код и двигаться дальше.Начиная с C # 8.0, вы должны иметь возможность предоставлять реализацию по умолчанию для вашего интерфейса, а не требовать универсального метода.
Реализация универсального метода и перегрузок для каждой
IPoint
реализации также нарушает принцип подстановки Лискова (L в SOLID). Было бы лучше внедрить алгоритм в каждуюIPoint
реализацию, а это значит, что вам нужен только один вызов метода:источник
Шаблон посетителя
в качестве альтернативы
dynamic
использованию вы можете использовать шаблон посетителя, как показано ниже:источник
Почему бы вам не определить функцию GetDim в классе и интерфейсе? На самом деле вам не нужно определять функцию GetDim, просто используйте свойство NumDims.
источник