Абстракция, препятствующая использованию пользовательских типов. Каким правилам следует следовать при реализации?
-
28-09-2019 - |
Вопрос
Я создал собственную систему типов для использования в сценариях C# внутри приложения.Скрипты компилируются «на лету» и позволяют взаимодействовать с внутренними данными приложения.Эта система типов спроектирована абстрактным образом с использованием таких интерфейсов, как IValue
.Реализация IValue
может быть RefString
, RefInteger
, RefDouble
(среди многих других, но этого достаточно для демонстрации моей проблемы).
Вот мы и подошли к моменту, когда я застрял...Использование этих IValue
объекты несколько неестественны.Считается хорошим решением всегда использовать интерфейсы для взаимодействия с объектами, но нет возможности определить неявное преобразование или перегрузку оператора для интерфейсов.Это приводит к ситуациям, когда уродливое явное приведение типов неизбежно, поэтому используется правильный оператор.
Пример:
IValue Add(IValue a, IValue b)
{
//return a+b; // won't work: which operator +() to use?
return (RefInteger)a + (RefInteger)b;
}
В случае C# в выражениях, включающих типы значений, предусмотрены неявные преобразования.Каков хороший способ разработки такой индивидуальной системы?
Я переписал систему типов, удалив IValue
интерфейс и введение RefValue
базовый класс.Таким образом уже можно устранить часть явного приведения типов.Я реализовал некоторую перегрузку операторов в этом базовом классе, но это вызвало много проблем с операторами преобразования по умолчанию...Помимо этого, логика, которую необходимо реализовать в реализации оператора, требует большого количества знаний о типах в системе.Я думаю, что в каком-то смысле это все еще тот путь, по которому нужно идти, но каковы правила, которым нужно следовать, чтобы реализовать это правильно и безопасно?
РЕДАКТИРОВАТЬ:После некоторых попыток мне удалось выяснить некоторые правила:
- Объявляйте неявно только операторы преобразования базовых типов (int, double, string,...)
- Объявите явным образом преобразования к базовым типам (чтобы избежать неявного приведения к int!!что случается довольно часто, но почему?)
- Чтобы избежать неоднозначных вызовов, операторы +, -, /, * не следует переопределять в базовом классе и в производных классах.[Каков тогда путь?Я выполнил перегрузку операции в производных классах, для этого требуется приведение типов при использовании, что опять же некрасиво...]
Решение
Если вы должны быть в состоянии сделать Add
операция на всех IValue
s, возможно, интерфейс должен включать Add
метод?Тогда вы могли бы сделать return a.Add(b);
, и внесите знания о том, как выполнить операцию, в каждый тип.
Одна из проблем заключается в том, что, как это выглядит сейчас, вы можете получать звонки там, где a
является RefString
и b
является RefInteger
, что, вероятно, не то, что вам нужно.Дженерики могут помочь это исправить:
T Add<T>(T a, T b) where T : IValue
{
return a.Add(b);
}
(Конечно, вам нужно будет добавить нулевую проверку и тому подобное)