Вы не предоставляете слишком много подробностей, поэтому здесь делается попытка ответить из того, что вы предоставляете.
Взгляните на основные отличия:
Если у вас есть базовый тип B
и производный тип D
, присваивание будет следующим:
B my_B_object = my_D_object;
назначает ссылку на тот же объект. С другой стороны, когда B
и D
являются независимыми типами с неявным преобразованием между ними, указанное выше присваивание создаст копию из my_D_object
и сохранит ее (или ссылку на нее, если B
это класс) на my_B_object
.
Таким образом, «настоящее» наследование работает по ссылке (изменения в ссылке влияют на объект, общий для многих ссылок), тогда как пользовательские преобразования типов обычно работают по значению (это зависит от того, как вы его реализуете, но реализуете что-то близкое к поведение "по ссылке" для конвертеров было бы почти безумным): каждая ссылка будет указывать на свой собственный объект.
Вы говорите, что не хотите использовать интерфейсы, но почему? Использование комбинированного интерфейса + вспомогательный класс + методы расширения (требуются C # 3.0 и .Net 3.5 или более поздняя версия) могут приблизиться к реальному множественному наследованию. Посмотрите на это:
interface MyType { ... }
static class MyTypeHelper {
public static void MyMethod(this MyType value) {...}
}
Выполнение этого для каждого «базового» типа позволит вам предоставить реализации по умолчанию для методов, которые вы хотите.
Они не будут вести себя как виртуальные методы из коробки; но вы можете использовать отражение, чтобы достичь этого; вам нужно будет сделать следующее в реализации класса Helper:
- восстановить
System.Type
с помощью value.GetType()
- найти, если этот тип имеет метод, соответствующий сигнатуре
- если вы найдете подходящий метод, вызовите его и вернитесь (так что остальная часть метода Помощника не будет запущена).
- Наконец, если вы не нашли конкретной реализации, позвольте остальной части метода работать и работать как "реализация базового класса".
Вот и все: множественное наследование в C # с единственным предупреждением о необходимости некоторого некрасивого кода в базовых классах, который будет поддерживать это, и некоторых накладных расходов из-за отражения; но если ваше приложение не работает под сильным давлением, это должно сработать.
Итак, еще раз, почему вы не хотите использовать интерфейсы? Если единственной причиной является их неспособность обеспечить реализацию методов, хитрость выше решает это. Если у вас есть какие-либо другие проблемы с интерфейсами, я мог бы попытаться разобраться с ними, но сначала я должен был бы узнать о них;)
Надеюсь, это поможет.
[РЕДАКТИРОВАТЬ: Добавление на основе комментариев]
Я добавил несколько деталей к исходному вопросу. Я не хочу использовать интерфейсы, потому что я хочу, чтобы пользователи не стреляли себе в ноги, неправильно внедряя их, или случайно вызывая метод (например, NewBar), который им нужно переопределить, если они хотят реализовать индикатор, но который они никогда не нужно звонить напрямую.
Я посмотрел на ваш обновленный вопрос, но комментарий довольно резюмирует его. Может быть, я чего-то упускаю, но интерфейсы + расширения + отражение могут решить все, что может сделать множественное наследование, и обходятся намного лучше, чем неявные преобразования при выполнении задачи:
- Поведение виртуального метода (предоставляется реализация, наследники могут переопределить): включить метод на помощнике (в отражении «виртуализация», описанном выше), не объявлять на интерфейсе. *
- Поведение абстрактного метода (реализация не предусмотрена, наследники должны реализовать): объявить метод в интерфейсе, не включать его в помощник.
- Поведение не виртуального метода (предоставляется реализация, наследники могут скрывать , но не может переопределить ): просто реализуйте его как обычно на помощнике.
- Бонус: странный метод (реализация предусмотрена, но наследники должны реализовать в любом случае; они могут явно вызывать базовую реализацию): это невозможно сделать с обычным или множественным наследованием, но я включаю его для завершенность: это то, что вы получите, если предоставите реализацию на помощнике и также объявите ее на интерфейсе. Я не уверен, как это будет работать (в аспекте виртуального или не виртуального) или как его использовать, но, эй, мое решение уже побило множественное наследование: P
Примечание. В случае невиртуального метода вам необходимо иметь тип интерфейса в качестве «объявленного» типа, чтобы обеспечить использование базовой реализации. Это точно так же, как когда наследник скрывает метод.
Я хочу, чтобы пользователи не стреляли себе в ноги, неправильно применяя их
Кажется, что не виртуальный (реализованный только на помощнике) будет работать лучше здесь.
или случайный вызов метода (например, NewBar), который они должны переопределить, если они хотят реализовать индикатор
Именно здесь абстрактные методы (или интерфейсы, которые являются своего рода супер-абстрактными вещами) сияют больше всего. Наследник должен реализовать метод, иначе код даже не скомпилируется. В некоторых случаях могут подойти виртуальные методы (если у вас есть общая базовая реализация, но разумнее использовать более конкретные реализации).
но они никогда не должны звонить напрямую
Если метод (или любой другой элемент) открыт клиентскому коду, но его не следует вызывать из клиентского кода, не существует программного решения, которое могло бы обеспечить это (на самом деле, есть со мной). Правильное место для адресации есть в документации. Потому что вы документируете свой API, не так ли? ;) Здесь вам не помогут ни преобразования, ни множественное наследование. Однако может помочь отражение:
if(System.Reflection.Assembly.GetCallingAssembly()!=System.Reflection.Assembly.GetExecutingAssembly())
throw new Exception("Don't call me. Don't call me!. DON'T CALL ME!!!");
Конечно, вы можете сократить его, если в вашем файле есть заявление using System.Reflection;
. И, кстати, не стесняйтесь менять тип и сообщение Исключения на что-то более описательное;).