Kalantyr
@Kalantyr

Множественное наследование в C# для свойств (или параметров функций)

Такая ситуация: определены несколько интерфейсов (например: IMyInterface1, IMyInterface2, ...). В классе MyClass есть свойство IMyInterface MyProperty { get; set; }.

Хотелось бы, чтобы свойство реализовывало сразу два-три интерфейса (примерно как это может делать класс) без потери строгой типизации.

Насколько знаю — синтаксис C# это не позволяет. Создавать на каждую связку интерфейсов отдельный тип не хочется (получится слишком много комбинаций). Создавать связку свойств, каждое из которых реализует отдельный интерфейс — это тоже как-то криво.

Наследование интерфейсов предлагаю не обсуждать, так как это нехорошо (наследование — лишь эффектный способ избежать дублирования кода). Интерфейсы независимые.

Какие есть предложения?



	interface IClickableButton
	{
		event Action Click;
	}

	interface IColorButton
	{
		Color Color { get; set; }
	}

	class MyForm
	{
		public IClickableButton ClickableButton { get; set; }
		public IColorButton ColorButton { get; set; }
		public <IClickableButton, IColorButton> ClickableColorButton { get; set; }
	}


Есть два интерфейса: «НажимабельнаяКнопка» и «ЦветнаяКнопка». Я хочу, чтобы свойство «НажимабельнаяЦветнаяКнопка» класса «МояФорма» как-нибудь реализовывало сразу оба этих интерфейса.

Сейчас мне приходится делать два отдельных свойства. Как их объединить в одно? (код <IColorButton, IColorButton> ClickableColorButton { get; set; } не прокатит, это я для понятности написал)

P. P. S.:

В конечном итоге пришли к вопросу "зачем это может быть нужно?" Согласен — задача редко встречающаяся. А цель одна — снижение взаимосвязи блоков кода.

Допустим, мы создаем классы-контроллеры, управляющие формами пользовательского интерфейса. Контроллеры работают не напрямую с классами, а через интерфейс. В большинстве случаев контроллеру достаточно подписываться на событие нажатия «НажимабельнойКнопки» и обрабатывать его. Но в один из двадцати контроллеров, например, не только обрабатывает клик, но и должен менять цвет этой кнопки. А еще пара контроллеров кроме клика должна менять текст на кнопке. Конечно, можно создать интерфейс сразу с «кликом», «цветом» и «текстом». Но ведь бОльшей части контроллеров остальные свойства вообще не нужны — им достаточно только клика. Вот и возникает задача — как бы передавать свойства «цвет» или «текст» только в тех редких случаях, когда они действительно необходимы.

И еще один момент — если код покрывается модульными тестами — в качестве подопытных объектов бывает нужно подсовывать mock-и. Чем проще интерфейсы, тем легче эти mock-объекты формировать.

Наверно, кто-то скажет что это уже излишнее абстрагирование, чересчур подробное — возможно… Но меня тут интересует — есть ли возможность. А в какой степени ее применять — это уже другой вопрос.

P. P. P. S.: подходящее для меня решение описано тут: habrahabr.ru/blogs/net/123229 Пока писал топик, пользователь tenbits предложил точно такое же решение.
  • Вопрос задан
  • 9203 просмотра
Пригласить эксперта
Ответы на вопрос 8
int02h
@int02h

interface IMyInterface1
{
    string Prop1 {get;set;}
    string Prop2 {get;set;}
}

interface IMyInterface2
{
    string Prop1 {get;set;}
    string Prop3 {get;set;}
}

class MyClass : IMyInterface1, IMyInterface2
{
    string Prop1 {get; set } // Общее свойство
    string Prop2 {get; set }
    string Prop3 {get; set }
}

Вы такую ситуацию имеете в виду? Если да, то так можно делать. Свойство Prop1 будет реализовывать сразу 2 интерфейса.
Ответ написан
int02h
@int02h
После уточнения вопроса стало несколько понятнее. Попробуйте через dynamic сделать:

public dynamic ClickableColorButton { get; set; }

Таким образом, Вы сможете положить в ClickableColorButton нужный вам объект, а потом в рантайме вызывать методы из разных интерфейсов.
Ответ написан
taliban
@taliban
php программист
Увы, не получится, на сколько мне известно.
Ответ написан
Комментировать
@bleykher
Возможно вам подойдёт что-то вроде:
class MyForm
{
public IClickableButton ClickableButton { get{return ClikableColorButton; }}
public IColorButton ColorButton { get {return ClickableColorButton; }}
public ClickableColorButton ClickableColorButton { get; set; }
}

нельзя возвращать ссылку сразу на 2 интерфейса, т.к. у класса может быть разная реализация одинаковых методов для разных интерфейсов и в этом случае будет непонятно, какой метод должен выполняться.
Ответ написан
Shedal
@Shedal
По-моему, вы не до конца продумали, как вы это хотите использовать. Вам же все равно прийдется создавать объект класса, который наследует оба интерфейса, а, значит, и создавать класс ColorClickableButton: IClickableButton, IColorButton.

В любом случае, вам, возможно, поможет конструкция типа этой:
public class MyForm<T> where T : IClickableButton, IColorButton
{
    T Button { get; set; }
}
Ответ написан
А можно воспользоваться свалкой:
        private static readonly Dictionary<Type, Object> Dump = new Dictionary<Type, object>();
        public T Get<T>() where T : class
        {
            return Dump.ContainsKey(typeof(T)) ? Dump[typeof(T)] as T : default(T);
        }
        public void Set<T>(T value) where T : class
        {
            Dump[typeof(T)] = value;
        }
Ответ написан
PashaPash
@PashaPash
В текущей версии C#, с сохранением строгой типизации — никак. C# не позволяет наследоваться от generic-параметров. Что-то вроде interface IDoubleInterface<I1, I2>: I1, I2 {} не заработает. Без генериков — строгой типизации не будет.

Не уверен насколько ситуация из P.P.S. жизненна, но в ней проблему связности нужно решать на другом уровне.
IMO, не стоит даже думать об сегрегации интерфейсов отдельных элементов управления, если каждый контроллер при этом получает форму с 50-ю полями, и может творить со всеми ее элементами что угодно. Попробуйте выделить интерфейсы из самой формы — возможно, многие контроллеры вообще не пересекаются по используемым свойствам.
Ответ написан
PashaPash
@PashaPash
Пример с БД не слишком очевиден без кода. Непонятно, зачем было перечислять интерфейсы в бизнес-логике. Наверняка можно было попробовать что-то вроде IoC/DI и рефлекшна для регистрации всех интерфейсов в IoC контейнере.
Решение с Tuples хорошее. Но хотелось бы понять, какую конкретно архитектурную проблему оно решает лучше, чем остальные методы.
Предлагаю подождать комментариев в хабратопике.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы