Ограничение IMyInterface<T> where T : IMyInterface<T>
От: thanks  
Дата: 04.07.09 19:31
Оценка:
Здравствуйте.

Объясните, пожалуйста, зачем используются подобные ограничения:
interface IMyInterface<T> where T : IMyInterface<T>
{
    T Get();
}

Честно говоря у меня от них мозг сносит и я никак не могу понять что они дают.
Просто наследие в ограничениях я понимаю, но T производное от этого-же или это же — не понимаю.
В примере ещё простой случай, но я где-то видел и навороченные варианты, там вообще не мог понять как что работает.
Re: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: Kore Sar  
Дата: 04.07.09 20:42
Оценка:
Здравствуйте, thanks, Вы писали:

T>Здравствуйте.


T>Объясните, пожалуйста, зачем используются подобные ограничения:

T>
T>interface IMyInterface<T> where T : IMyInterface<T>
T>{
T>    T Get();
T>}
T>

T>Честно говоря у меня от них мозг сносит и я никак не могу понять что они дают.
T>Просто наследие в ограничениях я понимаю, но T производное от этого-же или это же — не понимаю.
T>В примере ещё простой случай, но я где-то видел и навороченные варианты, там вообще не мог понять как что работает.

Ну, это почти как:
class MyClass
{
  MyClass Get();
}


Дерево объектов, что тут сложного?
Re: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: IT Россия linq2db.com
Дата: 04.07.09 21:10
Оценка:
Здравствуйте, thanks, Вы писали:

T>Честно говоря у меня от них мозг сносит и я никак не могу понять что они дают.


Конкретным типом такого дженерик типа может быть только наследник этого типа.
Если нам не помогут, то мы тоже никого не пощадим.
Re: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: Silver_s Ниоткуда  
Дата: 05.07.09 16:00
Оценка: +2 -2 :)
Здравствуйте, thanks, Вы писали:

T>Здравствуйте.


T>Объясните, пожалуйста, зачем используются подобные ограничения:

T>
T>interface IMyInterface<T> where T : IMyInterface<T>
T>{
T>    T Get();
T>}
T>

T>Честно говоря у меня от них мозг сносит и я никак не могу понять что они дают.
T>Просто наследие в ограничениях я понимаю, но T производное от этого-же или это же — не понимаю.
T>В примере ещё простой случай, но я где-то видел и навороченные варианты, там вообще не мог понять как что работает.

Мозги должно сносить от примеров где интерфейс наследуется сам от себя или от своего будущего наследника.C# это к счастью не дает делать
А тут ... возникают подозрения на кривые руки писателя такого interface IMyInterface<T> where T : IMyInterface<T>
Потому что сходу не удается придумать сценарий использования, где бы эта штука пользу принесла — т.е. понятность программы повысила или объем ручного труда понизила. А если цель что-то заумное написать,чтоб другие не смогли понять ... ну давно известно что заумную штуковину проще создать чем понять и с пользой применять.

Если пойти логическим путем:
1) Этот интерфейс IMyInterface, пригоден только для наследования. Такую переменную создать невозможно IMyInterface<IMyInterface> mi =...
2) Использоваться будет видимо так
interface I1 : IMyInterface<I1>
{
//...
}
interface I2 : IMyInterface<I2>
{
//...
}

3) Отконвертировать I1 в I2 не получится. И даже общего базового интерфейса у них фактически нет. Класс реализующий I1 можно использовать только через IMyInterface<I1> , или через I1.

4) Похоже единственное для чего это может использоваться это для автоматизации написания интерфейсов I1,I2. Скажем, есть у них похожая часть в виде 20 функций, их можно скопировать изменяя кое-где типы.А можно извратиться как указано выше. Но такие интерфейсы уже сами по себе корявые.
Сложно пердставить сценарии, где нужно строгать такие интерфейсы в больших количествах.
И даже если такие сценарии и есть, ИМХО, лучше copy-paste чем такое уродство.

5)Есть еще теоретическая возможность использовать его для наслеования реализаций.
Т.е. некий класс ImplIMyInterface реализует IMyInterface<T>, затем классы реализуют интерфейсы I2,I1 используя уже готовую реализацию ImplIMyInterface.
Но непонятно можно ли таким образом реализовать IMyInterface<T>.

Так чтоли это делать?
interface IMyInterface<T> where T : IMyInterface<T>
{
   T Prop { get; }
}
class ImplIMyInterface<T>:IMyInterface<T> where T:IMyInterface<T> 
{
   public T Prop
   {
      get {...}
   }
}
interface I2 : IMyInterface<I2> {}
class Impl2 : ImplIMyInterface<I2>, I2 {}




Ну пусть даже компилятор не станет ругается на это безобразие, но все равно это бессмыслица.
В классе ImplIMyInterface пропертя T Prop не сможет вернуть this, не сможет и вернуть (IMyInterface<T>)this, хотя класс делает вид, что реализует интерфейс IMyInterface<T>

Вобще непонятно зачем такой интерфейс может понадобиться.

Может по рукам надо бить, тех кто такие интерфейсы создает?
Re: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: maloi_alex СССР  
Дата: 05.07.09 17:39
Оценка:
Здравствуйте, thanks, Вы писали:

T>Здравствуйте.


T>Объясните, пожалуйста, зачем используются подобные ограничения:


В данном случае это ограничение идет на пользу потому что, вводит более строгую типизацию. Т.е. контроль компиляции, ну и еще IntelliSense так как тип значения уже определен.
в приведеннии типа
Re[2]: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: Silver_s Ниоткуда  
Дата: 05.07.09 21:58
Оценка:
S_>В классе ImplIMyInterface пропертя T Prop не сможет вернуть this...

Проверил...хотя this вернуть все же можно. Если явно тип преобразовать два раза. И не догадаешься сразу...

class ImplIMyInterface<T>:IMyInterface<T> where T:IMyInterface<T> 
{
   public T Prop
   {
      get { return (T)(IMyInterface<T>)this; }
   }
}


Но все равно криво.
Re[3]: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: _Dreamer Россия  
Дата: 06.07.09 02:56
Оценка:
Здравствуйте, Silver_s, Вы писали:

S_>>В классе ImplIMyInterface пропертя T Prop не сможет вернуть this...


S_>Проверил...хотя this вернуть все же можно. Если явно тип преобразовать два раза. И не догадаешься сразу...


S_>
S_>class ImplIMyInterface<T>:IMyInterface<T> where T:IMyInterface<T> 
S_>{
S_>   public T Prop
S_>   {
S_>      get { return (T)(IMyInterface<T>)this; }
S_>   }
S_>}
S_>


S_>Но все равно криво.


это как, простите ?
а если было так —
class DummyNode : IMyInterface<DummyNode> 
    {
        public DummyNode Prop { get { return this; } }
    }

...

var ii = new ImplIMyInterface<DummyNode>();
var prop = ii.Prop; // System.InvalidCastException


кто ж гарантирует, что тип T и ImplIMyInterface<T> — это одно и тоже ?
Re[4]: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: _FRED_ Черногория
Дата: 06.07.09 06:56
Оценка:
Здравствуйте, _Dreamer, Вы писали:

_D>кто ж гарантирует, что тип T и ImplIMyInterface<T> — это одно и тоже ?


Если требуется, то соответствующую проверку можно добавить в статический конструктор реализации.
Help will always be given at Hogwarts to those who ask for it.
Re[2]: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: HowardLovekraft  
Дата: 06.07.09 07:30
Оценка:
Здравствуйте, Silver_s, Вы писали:

S_>Вобще непонятно зачем такой интерфейс может понадобиться.


Есть иерархия интерфейсов и их реализации. Пусть это будет нечто древовидное, описывающее иерархию типа "родитель-потомок":
    // Интерфейсы
    public interface IBaseInterface
    {
        IBaseInterface Parent { get; set; }
    }

    public interface IDerivedInterface1 : IBaseInterface
    {
    }

    public interface IDerivedInterface2 : IBaseInterface
    {
    }

    // Реализации
    public class DerviedClass1 : IDerivedInterface1
    {
        public IBaseInterface Parent
        {
            ...
        }
    }

    public class DerivedClass2 : IDerivedInterface2
    {
        public IBaseInterface Parent
        {
            ...
        }
    }


А теперь я хочу разделить ветви наследования. Мне нужно, чтобы отныне все хомячки происходили только от хомячков, а суслики — от сусликов:

1. Для этого нужно запретить указывать в качестве Parent хомячка другое животное. Причем мне не нужны генетические уроды, которых я буду усыплять после рождения — нужна проверка на этапе компиляции, а не в рантайме.
2. Теперь я точно знаю, что хомячки происходят только от себе подобных. Следовательно, нет необходимости каждый раз кастить свойство Parent к нужному типу.

Это как раз решается таким образом:
    // Интерфейсы
    public interface IBaseInterface<T>
        where T : IBaseInterface<T>
    {
        T Parent { get; set; }
    }

    public interface IDerivedInterface1 : IBaseInterface<IDerivedInterface1>
    {
    }

    public interface IDerivedInterface2 : IBaseInterface<IDerivedInterface2>
    {
    }

    // Реализации
    public class DerviedClass1 : IDerivedInterface1
    {
        public IDerivedInterface1 Parent
        {
            ...
        }
    }

    public class DerivedClass2 : IDerivedInterface2
    {
        public IDerivedInterface2 Parent
        {
            ...
        }
    }


Если этому есть более удобная альтернатива, приведите ее — буду благодарен.
Re[4]: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: Silver_s Ниоткуда  
Дата: 06.07.09 12:53
Оценка:
Здравствуйте, _Dreamer, Вы писали:

S_>>
S_>>class ImplIMyInterface<T>:IMyInterface<T> where T:IMyInterface<T> 
S_>>{
S_>>   public T Prop
S_>>   {
S_>>      get { return (T)(IMyInterface<T>)this; }
S_>>   }
S_>>}
S_>>


_D>
_D>class DummyNode : IMyInterface<DummyNode> 
_D>    {
_D>        public DummyNode Prop { get { return this; } }
_D>    }
_D>...
_D>var ii = new ImplIMyInterface<DummyNode>();
_D>var prop = ii.Prop; // System.InvalidCastException 
_D>

_D>кто ж гарантирует, что тип T и ImplIMyInterface<T> — это одно и тоже ?

Здесь runtime ошибка происходит фактически из-за того что класс ImplIMyInterface<DummyNode> пытаются отконвертировать в класс DummyNode.
А он не является его базовым классом.
Этим и плох такой класс ImplIMyInterface<T>, что не все можно подставлять вместо T. А ограничения на этапе компиляции для этого случая задать не получится. Т.к. для параметра T в where указываются типы для которых T является "assignabale TO", а тут надо указать типы для которых T "assignabale From". Т.е. как-то так class ImplIMyInterface<T>......where this:T ....

использовать ImplIMyInterface получится видимо только так.
interface I2 : IMyInterface<I2> {}
class Impl2 : ImplIMyInterface<I2>, I2 {}

А если убрать из реализации I2
class Impl2 : ImplIMyInterface<I2> /*,I2*/ {}
Тогда ошибка поймается только в runtime.
Re[3]: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: Silver_s Ниоткуда  
Дата: 06.07.09 15:43
Оценка:
Здравствуйте, HowardLovekraft, Вы писали:

HL>А теперь я хочу разделить ветви наследования. Мне нужно, чтобы отныне все хомячки происходили только от хомячков, а суслики — от сусликов:

HL>Это как раз решается таким образом:
HL>
HL>    // Интерфейсы
HL>    public interface IBaseInterface<T>
HL>        where T : IBaseInterface<T>
HL>    {
HL>        T Parent { get; set; }
HL>    }
HL>    public interface IDerivedInterface1 : IBaseInterface<IDerivedInterface1>
HL>    {
HL>    }
HL>    // Реализации
HL>    public class DerviedClass1 : IDerivedInterface1
HL>    {
HL>        public IDerivedInterface1 Parent
HL>        {
HL>            ...
HL>        }
HL>    }
HL>

HL>Если этому есть более удобная альтернатива, приведите ее — буду благодарен.

Создать типизированое дерево, конечно дело полезное. Но вариантов всегда много. Какой вариант удобнее,все таки определяется конкретными деталями использования,метриками.

В связи с этим кодом возникают такие вопросы:
1) Если классы DerviedClass1,2 не наследуются от базового класса, а напрямую реализуют интерфейсы IDerivedInterface1. Тогда вариант copy-paste по функциональности ничем не отличаются.
Вместо:
public interface IDerivedInterface1 : IBaseInterface<IDerivedInterface1>
{
}

Такой:
public interface IDerivedInterface1 
{
  IDerivedInterface1 Parent { get; set; }
}

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

2) Вероятно возникнет потребность обрабатывать эти иерархические банды хомячков и сусликов, универсальным образом.
Сделать это все-таки можно, спасает автоматический вывод типов. Так:
void Func<T>(IMyInterface<T> f)  where T:IMyInterface<T>
{
   var v=f.Prop;
}

В этом случае, конечно, отличаи от copy-paste принципиальные. Т.к. сам базовый интерфейс можно использовать.
Но один раз базовый интерфейс на это подсадишь, весь код потом будет усыпан этими where T:IMyInterface<T>.
У человека мозги сносило от одного только объявления такого интерфейса, а тут в каждой функции where T:IMyInterface<T>
Ладно уговорили, руки отбивать не стоит, только за один такой интерфейс. Может и существуют ситуации где в крайнем случае это можно применить, пожертвовать чистотой кода.
Но обычные деревья врядли так стоит делать.

----------

Что касается именно деревьев.
Возможны всякие спекуляции, наподобие таких:
class TreeNode<T>
{
  TreeNode<T> Parent { get; set; }
  T TagObject { get; set; }
}


Или различные статические затемплейченые функции обработки деревьев. Которые с любыми нодами работают, доступ к дочерним получают через делегат.
Например с такими декларациями: ... EnumAllTreeNodes<T>(T root, Func<T,IEnumerable<T>> getChilds);
Использующиеся примерно так: EnumAllTreeNodes(root, ( e=> e.GetChildNodes() ));
Таким функциям пофигу хомячки это или суслики, и неважно как называются методы или проперти GetChilds()

итд.
Re[3]: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: Silver_s Ниоткуда  
Дата: 06.07.09 17:49
Оценка:
Здравствуйте, HowardLovekraft, Вы писали:

HL>Есть иерархия интерфейсов и их реализации. Пусть это будет нечто древовидное, описывающее иерархию типа "родитель-потомок":

HL>1.... Для этого нужно запретить указывать в качестве Parent хомячка другое животное.

Кстати еще не факт что этот Parent вобще нужен в интерфейсах хомячков.
А если надо связанный список хомячков сделать. Не уж то так будешь делать:

public interface IBaseInterface<T> where T : IBaseInterface<T>
{
      T Next{ get; set; }
      T Prev{ get; set; }
}


А потом каждого хомячка,суслика от него наследовать.
Для связанных списков вобще нельзя делать реализацию элементов, через наследование. (Это раньше так делали, теперь уже не модно)
Единственный удачный вариант это как в стандартной библиотеке (даже sealed):
public sealed class LinkedListNode<T>
{
     public LinkedListNode(T value);
     public LinkedList<T> List { get; }
     public LinkedListNode<T> Next { get; }
     public LinkedListNode<T> Previous { get; }
     public T Value { get; set; }
}


Хомячки отдельно, стандартные структуры данных отдельно.
Но стандартные структуры это не только связанный список ... деревьев,графов это тоже касается.

А если для построения дерева, у этих хомячков захочется не Parent указывать, а список Childs? А к базовому интерфейсу уже нет доступа (по разным причинам), или просто уже нельзя его менять?
Re[4]: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: HowardLovekraft  
Дата: 06.07.09 19:55
Оценка:
Здравствуйте, Silver_s, Вы писали:

S_>1) Если классы DerviedClass1,2 не наследуются от базового класса, а напрямую реализуют интерфейсы IDerivedInterface1. Тогда вариант copy-paste по функциональности ничем не отличаются.

Жесть.
Даже если они не наследуются от базового класса, добавьте в IBaseInterface с десяток методов и свойств. Далее представьте, что IDerivedInterfaceN может быть с десяток, и кроме того, каждый из них определяет свои методы. Заодно представьте, что это — библиотека и размер ее API Guide.

Вы на самом деле считаете это приемлемым, копипастить? Вы затраты на сопровождение не считаете — а что, если пришлось добавить метод (изначально наследование интерфейсов было добавлено не просто так)?

S_>2) Вероятно возникнет потребность обрабатывать эти иерархические банды хомячков и сусликов, универсальным образом.

Да, разумеется, возникнет.

S_>Но один раз базовый интерфейс на это подсадишь, весь код потом будет усыпан этими where T:IMyInterface<T>.

S_> У человека мозги сносило от одного только объявления такого интерфейса, а тут в каждой функции where T:IMyInterface<T>
Усыпан, и что? Вы сейчас советуете не писать код с дженериками, бо он вами и топикстартером плохо читается?

S_>Но обычные деревья врядли так стоит делать.

Что такое "обычное дерево" и чем оно отличается от "необычного дерева". Четкое определение, пожалуйста.

S_>Что касается именно деревьев.

S_>Возможны всякие спекуляции, ... Которые с любыми нодами работают, доступ к дочерним получают через делегат.
Не нужен "любой нод". Нужен нод конкретного типа, и обработать его нужно как нод конкретного типа. Через какой делегат нужно обратится к Parent в вашем варианте, чтобы сделать вот так, без приведения типов, и как гарантировать, что приведение типов завершится удачно:
    public interface IDerivedInterface1 : IBaseInterface<IDerivedInterface1>
    {
        void Foo1();
    }

    public interface IDerivedInterface2 : IBaseInterface<IDerivedInterface2>
    {
        void Foo2();
    }

    public class DerivedClass1 : IDerivedInterface1
    {
        void SomeMethod1()
        {
           Parent.Foo1();
        }
...
    }

    public class DerivedClass2 : IDerivedInterface2
    {
        void SomeMethod2()
        {
           Parent.Foo2();
        }
...
    }

Набросайте пример.

Еще:
public interface IMyInterface<T> : IAnotherBaseInerface

Т.е. вполне может быть "более" базовый интерфейс, совсем не дженерик.

З.Ы. Еще пример, более конкретный — описание иерархии метаданных типа "1С", но с наследованием. Справочники, документы и регистры — это объекты метаданных. При этом справочник может наследоваться от справочника, документ — от документа, etc. Плюс есть элементы метаданных, которые не наследуются — перечисления. Плюс конфигурация, которая содержит иерархии справочников, документов, регистров и которая сама по себе — элемент метаданных.

S_>GetChilds

З.З.Ы. Блин, children.
Re: Ограничение IMyInterface<T> where T : IMyInterface<T>
От: Аноним  
Дата: 06.07.09 20:09
Оценка: 2 (1) +1
Здравствуйте, thanks, Вы писали:

T>Здравствуйте.


T>Объясните, пожалуйста, зачем используются подобные ограничения:

T>
T>interface IMyInterface<T> where T : IMyInterface<T>
T>{
T>    T Get();
T>}
T>


Вообще, это C#-я версия хорошо известного в C++ кругах Curiously recurring template pattern
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.