C#, generics, зацените!

bleyman

Я думал, что шарповые генерики очень простые и ненавязчивые.
Я был неправ.
Заценяйте код (пригодный для компиляции и запуска):

using System.Collections.Generic;
public interface IConfigurationNode
{
//IConfigurationNode GetOrCreateSubnode(string key);
//string this[string name] {get; set}
//and other stuff like this
}
public abstract class ConfigurationNodeBase <ActualNodeType> : IConfigurationNode
where ActualNodeType: ConfigurationNodeBase<ActualNodeType>
{
protected Dictionary<string, ActualNodeType> children;
protected abstract ActualNodeType SpawnChildNode(string key);
}
public class XmlConfigurationNodeGeneric<ActualNodeType> : ConfigurationNodeBase<ActualNodeType>
where ActualNodeType : XmlConfigurationNodeGeneric<ActualNodeType>, new
{
protected ActualNodeType parent;

protected override ActualNodeType SpawnChildNode(string key)
{
ActualNodeType newNode = new ActualNodeType;
newNode.parent = (ActualNodeType)this;
return newNode;
}
}
public sealed class XmlConfigurationNode : XmlConfigurationNodeGeneric<XmlConfigurationNode>
{ }
public class Test
{
public static void Main
{
XmlConfigurationNode test = new XmlConfigurationNode;
}
}

ConfigurationNodeBase - типа базовый класс, реализующий функциональность интерфейса, рассчитаного на древовидную структуру. Вот так вот мило отселфреференсились, причём в процессе шарп понял, что ActualNodeType тоже держит IConfigurationNode.
XmlConfigurationNodeGeneric - вот это (не знаю, как именно назвать) реализует конкретное деревце, умеющее сейвиться в хмл. Замечу, что от него можно наследоваться дальше в том же духе. Ещё замечу, что можно писать newNode.parent (потому что она как бы наследуется от нас). Ещё замечу, что в субклассах можно не оверрайдить SpawnNewNode - она будет выдавать объекты правильного типа. На самом деле её даже можно перенести в базовый класс, только нафиг?
XmlConfigurationNode - а это мы конкретно инстанцировались для тех, кто хочет использовать XmlConfigurationNodeGeneric как она есть. Он так и выглядит - пустой и sealed - чтобы наследовались именно от генерика.
При этом в XmlConfigurationNodeGeneric (и в её предположительных потомках) коллекция children оказывается нужного типа. Ну и всё остальное тоже.
Воооооот.
Я это случайно придумал, потом на всякий случай проверил, и оказалось, что действительно можно генериками как бы рекурсивно наследоваться. Потом я ещё минут двадцать пытался понять, кто от кого наследуется и действительно ли получилось то, что я хотел. Вроде да. Но как-то крышу сносит немножко. Удивительно. То есть честно говоря я до сих пор не очень понимаю, что там от чего наследуется - моск уходит в рекурсию. Но могу прогать, если не задумываться =)
Да, что в таком случае происходит с полиморфизмом я не представляю. То есть если мы, например, напишем какую-нибудь CachingXmlNodeGeneric и инстанцируемся в CachingXmlNode, можно ли будет тайпкастить CachingXmlNode в XmlNode? А наоборот? Загадка...
ЗЫ: А хуле тег code захавывает пустые строки?

Helga87

Летом, когда активно экспериментировал с генериками в одном проекте, в том числе получая и такого рода конструкции, сделал для себя вывод, что, пытаясь сделать с помощью них что-то нетривиальное, почти всегда появляется громоздкость и исчезает ясность происходящего. Остается только говорить: но ведь работает! - а это не слишком интересная для реального использования ситуация.

bleyman

Зато кастов намного меньше.
А ясность происходящего у меня уже почти появилась =)

anzakaznov

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

bleyman

Ну, мы как бы говорим компилятору, что ActualNodeType - это тот тип, который нам передадут при инстанцировании генерика, и что этот тип унаследован от нас. Каждый раз, когда мы наследуемся и пишем новый генерик, мы видим, что ActualNodeType - наш наследник.
Как именно компилятор это всё понимает - хз =)

Dasar

> Как именно компилятор это всё понимает - хз =)
а он и не понимает, реально же - подставляемые типы не компилируются

bleyman

А когда я пишу
public sealed class XmlConfigurationNode : XmlConfigurationNodeGeneric<XmlConfigurationNode> {}
Что происходит, по твоему? Компилятор волевым усилием сворачивает рекурсивное определение в самый обыкновенный класс, не так ли?

Helga87

Нет. Компилятор делает вот что. Такой код: 
public sealed class Lala : List<int>
{
}
Он превращает в такой IL: 
.class public auto ansi sealed beforefieldinit Lala
     extends [mscorlib]System.Collections.Generic.List`1<int32>
{
     .method public hidebysig specialname rtspecialname instance void .ctor cil managed
     {
     }
}
И только в момент инициализации типа Lala в рантайме произойдет компиляция базового генериковского класса с уже подставленным параметром int.

bleyman

Ладно, какая разница, кто именно делает это непонятное действие - csc или рантайм?

Helga87

В рантайме все просто - у нас уже есть вся информация. Действия просты:
1. Хотим инициализировать тип XmlConfigurationNode. Загрузили список полей и методов, которые относятся непосредственно к этому типу (т.е. те, которые можно получить с помощью typeof(XmlConfigurationNode).GetMembers с разными флагами).
2. После этого инициализируем предка. Для этого делаем экземпляр типа XmlConfigurationNodeGeneric, параметризованный XmlConfigurationNode.
Метаданные в .NET хранятся в реляционной БД, поэтому, чтобы представить описанные действия даже мозги ломать не нужно.

Dasar

> Что происходит, по твоему?
компилятор просто помечает, что такая переменная имеет тип bla-bla.
> Компилятор волевым усилием сворачивает рекурсивное определение в самый обыкновенный класс, не так ли?
так никакой рекурсии и нет.
при подстановке конкретного типа - опять же просто помечается, что данная переменная может конвертиться к этому типу.
возьмем вот такой код:

class A<T>
{
T a;
public T GetA
{
return a;
}
}

в утрированном виде - при подстановке B в качестве T - компилятор просто это превращает в код

class A
{
[Пусть будет object, но я помню - что это "B"]
object a;
[Возвращаем object, но помним что это "B"]
public object GetA
{
return a;
}
}

для сложно типа все тоже самое:

class A
{
[Пусть будет object, но я помню - что это "XmlConfigurationNodeGeneric<XmlConfigurationNode>"]
object a;
[Возвращаем object, но помним что это "XmlConfigurationNodeGeneric<XmlConfigurationNode>"]
public object GetA
{
return a;
}
}

причем вот это "помним" - влияет только на то, что не делается type cast, когда мы присваимся переменного того же типа (или предка).
а сравнение типов - происходит просто на равенство, грубо говоря просто fullname сравниваем - да и все.
соответственно никакой рекурсии для компилятора нет.
Оставить комментарий
Имя или ник:
Комментарий: