Здравствуйте, 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>
Объясните, пожалуйста, зачем используются подобные ограничения:
interface IMyInterface<T> where T : IMyInterface<T>
{
T Get();
}
Честно говоря у меня от них мозг сносит и я никак не могу понять что они дают.
Просто наследие в ограничениях я понимаю, но T производное от этого-же или это же — не понимаю.
В примере ещё простой случай, но я где-то видел и навороченные варианты, там вообще не мог понять как что работает.
Re: Ограничение IMyInterface<T> where T : IMyInterface<T>
Здравствуйте, 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>
Здравствуйте, thanks, Вы писали:
T>Здравствуйте.
T>Объясните, пожалуйста, зачем используются подобные ограничения:
В данном случае это ограничение идет на пользу потому что, вводит более строгую типизацию. Т.е. контроль компиляции, ну и еще IntelliSense так как тип значения уже определен.
Здравствуйте, 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>
Здравствуйте, 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>
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>
Здравствуйте, 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>
{
}
Насколько я понимаю, основные затраты времени,труда (при написании или изучении кода) будут не на написание таких похожих интерфейсов, а на их реализацию.
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>
Здравствуйте, 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>
Здравствуйте, 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.