Как унаследовать интерфейс от класса?

303
19 ноября 2017, 13:19

Есть два класса, наследника стандартных элементов управления winforms. Оба класса реализуют мой интерфейс IMyControl

public class MyUserControl : UserControl, IMyControl
{   
   public void Foo1 (int int1, int int2)
   {
       //реализация Foo1 
   }
   public void Foo2 (string string1)
   {
       //реализация Foo2
   }
}
public class MyPanel : Panel, IMyControl
{   
   public void Foo1 (int int1, int int2)
   {
       //реализация Foo1 
   }
   public void Foo2 (string string1)
   {
       //реализация Foo2
   }
}
public interface IMyControl
{   
   void Foo1 (int int1, int int2);
   void Foo2 (string string1);
}

Теперь мне нужно реализовать класс-хэлпер следующего вида

public class MyControlHelper<T> where T : IMyControl
{
     public void BuildTo(Control body, T child)
     {
         body.Controls.Add(child);
     }
}

Хорошо, я знаю что мои классы реализующие IMyControl можно добавить в дочерние к иному контролу. Но компилятор не знает. Хорошо, значит надо указать, что мой интерфейс включает IControl и всего делов

public interface IMyControl : IControl
{   
   void Foo1 (int int1, int int2);
   void Foo2 (string string1);
}

Ой. Но интерфейса IControl в winforms почему-то нет. А какой же тип принимает в качестве параметра метод Control.Controls.Add ? Оказывается он принимает тип класса Control. Вот это уже как-то нехорошо... Может интерфейс в c# всё-таки может включать "интерфейс класса"?

public interface IMyControl : Control
{   
   void Foo1 (int int1, int int2);
   void Foo2 (string string1);
}

Увы, нет (Что сложно было сделать?!)

Так как же мне реализовать MyControlHelper ?

Answer 1

Самый простой способ - поставить второе ограничение там, где это требуется:

public class MyControlHelper<T> where T : Control, IMyControl
{
     public void BuildTo(Control body, T child)
     {
         body.Controls.Add(child);
     }
}

В тех же местах, где такой вариант неприемлем - можно оставить только наиболее важное ограничение, а остальные сделать опциональными:

public void BuildTo(Control body, Control child)
{
    body.Controls.Add(child);
}
public IEnumerable<IMyControl> GetMyControls(Control body)
{
    // Нас интересуют только IMyControl, на остальные просто не обращаем внимания
    return body.Controls.OfType<IMyControl>();
}

Более сложный вариант - инвертировать логику добавления: пусть контрол сам думает как правильно добавиться к родителю.

public interface IMyControl 
{   
   void AttachTo(Control body);       
   void Foo1 (int int1, int int2);
   void Foo2 (string string1);
}
public class MyControlHelper<T> where T : IMyControl
{
     public void BuildTo(Control body, T child)
     {
         child.AttachTo(body);
     }
}

Но тут надо помнить о том, что контрол вполне может сделать эту операцию как-то нестандартно, а потому надо хранить дополнительную коллекцию IMyControl если в дальнейшем требуется добавленные таким образом контролы достать обратно:

public interface IMyControl 
{   
   void AttachTo(Control body);       
   void Foo1 (int int1, int int2);
   void Foo2 (string string1);
}
public class MyControlHelper<T> where T : IMyControl
{
     static readonly ConditionalWeakTable<Control, List<T>> 
         childs = new ConditionalWeakTable<Control, List<T>>();
     public void BuildTo(Control body, T child)
     {
         child.AttachTo(body);
         childs.GetOrCreateValue(body).Add(child);
     }
     public IEnumerable<T> GetChilds(Control body) 
         => childs.GetOrCreateValue(body);
}
Answer 2

Можно добавить в интерфейс метод AsControl()

public interface IMyControl 
{   
   Control AsControl();
   void Foo1 (int int1, int int2);
   void Foo2 (string string1);
} 

реализацию метода будет простая

public Control AsControl()
{
   return this as Control;   
}   

код хэлпера, изменится лишь немного

public class MyControlHelper<T> where T : IMyControl
{
     public void BuildTo(Control body, T child)
     {
         body.Controls.Add(child.AsControl());
     }
}
UPD

Ну и в отличии от варианта от tym32167 здесь всё-таки вешается обязанность приводится к интерфейсу типа Control, на классы реализующие IMyControl, а не откладывается соответствие обязанности до вызова метода хэлпера.

READ ALSO
Получить реальный размер формы

Получить реальный размер формы

Хочу получить размеры формы когда она полностью развёрнута

233
IQueryable в Entity Framework

IQueryable в Entity Framework

Я так поняла, что если у меня данные IQueryable, то запрос выполняется, только когда я вызываю методы Count(), ToList() и подобные, либо начинаю перебирать...

211