2012-02-20 28 views
5

В настоящее время я работаю над простым способом реализации интрузивной древовидной структуры на C#. Поскольку я в основном программист на C++, я сразу же хотел использовать CRTP. Вот мой код:C# - Интрузивная древовидная структура с использованием CRTP

public class TreeNode<T> where T : TreeNode<T> 
{ 
    public void AddChild(T a_node) 
    { 
     a_node.SetParent((T)this); // This is the part I hate 
    } 

    void SetParent(T a_parent) 
    { 
     m_parent = a_parent; 
    } 

    T m_parent; 
} 

Это работает, но ... Я не могу понять, почему я должен бросить при вызове a_node.SetParent ((T) это), так как я использую родовое ограничение типа .. . C# литая имеет цену, и я хотел бы, чтобы не распространять этот бросок в каждом навязчивого реализации коллекции ...

+0

Я позволил себе упростить ваш пример. Пожалуйста, верните, если вы не согласны. – usr

+1

Если честно, это выглядит как умный голова ** k. Что не так с более традиционными способами представления деревьев с использованием композиции? Что это вы покупаете? Надеюсь, это звучит не слишком антагонистично. Мне просто интересно. – spender

+0

@spender его половину количества распределений, и уменьшает количество косвенных действий, которые вам нужно следовать. Поэтому в коде высокой производительности это может быть разумным компромиссом. Для небольших деревьев это, вероятно, плохая идея. – CodesInChaos

ответ

3

это, по крайней мере, тип TreeNode. Он может быть выведен или может быть точно TreeNode. SetParent ожидает T. Но T может быть другого типа, чем это. Мы знаем, что это и T оба происходят из TreeNode, но они могут быть разных типов.

Пример:

class A : TreeNode<A> { } 
new TreeNode<A>() //'this' is of type 'TreeNode<A>' but required is type 'A' 
0

Никто не гарантирует, что T и тип this одинаковы. Они могут быть даже несвязанными подклассами TreeNode.

Вы ожидаете, что T будет использоваться в любопытно повторяющемся шаблоне шаблона, но общие ограничения не могут это выразить.

Глупая реализация может быть определена как StupidNode:TreeNode<OtherNode>.

0

Проблема с этой линии:

TreeNode<T> where T : TreeNode<T> 

Т будучи TreeNode является рекурсивное определение оно не может быть определено заранее или даже компилировать статически проверяется. Не используйте шаблон, или если вы вам нужно реорганизовать & отделить узел от полезной нагрузки (т.е. узла данные от самого узла.)

public class TreeNode<TPayload> 
{ 
    TPayload NodeStateInfo{get;set;} 

    public void AddChild(TreeNode<TPayload> a_node) 
    { 
     a_node.SetParent(this); // This is the part I hate 
    } 

    void SetParent(TreeNode<TPayload> a_parent) 
    { 
    } 
} 

Кроме того, я не знаю, почему вы звоните a_node .SetParent (это). Похоже, что AddChild более точно назван SetParent, потому что вы устанавливаете этот экземпляр как родительский элемент a_node. Может быть, это какой-то эзотерический алгоритм, с которым я не знаком, иначе он выглядит не так.

+0

В то время как коллекции, основанные на композиции, обычно являются лучшей идеей, навязчивые коллекции имеют свое место. 'TreeNode , где T: TreeNode ' ограничение имеет смысл в этом контексте. Его можно удовлетворить любопытно повторяющимся шаблоном шаблона. Реализация 'AddChild' выглядит также хорошо для меня. В дереве вы либо реализуете «SetParent», либо «AddChild» явно, а затем заставляете другого вызывать тот, который вы реализовали. – CodesInChaos

0

Рассмотрим, что произойдет, если мы отклоняемся от CRTP конвенции, написав ...

public class Foo : TreeNode<Foo> 
{ 
} 

public class Bar : TreeNode<Foo> // parting from convention 
{ 
} 

... и затем вызвать приведенный выше код следующим образом:

var foo = new Foo(); 
var foobar = new Bar(); 
foobar.AddChild(foo); 

AddChild вызов бросает InvalidCastException говоря Unable to cast object of type 'Bar' to type 'Foo'.

Что касается идиомы CRTP - это только соглашение, требующее, чтобы общий тип был таким же, как декларация типа. Язык должен поддерживать другие случаи, когда соглашение CRTP не соблюдается. Эрик Липперт написал замечательное сообщение в блоге по этой теме, которое он связал с этим другим crtp via c# answer.

Все, что сказал, если вы изменить реализацию на это ...

public class TreeNode<T> where T : TreeNode<T> 
{ 
    public void AddChild(T a_node) 
    { 
     a_node.SetParent(this); 
    } 

    void SetParent(TreeNode<T> a_parent) 
    { 
     m_parent = a_parent; 
    } 

    TreeNode<T> m_parent; 
} 

... выше код, который ранее бросил InvalidCastException теперь работает.Изменение составляет m_Parent тип TreeNode<T>; что делает this либо тип T, как и в случае Foo класса или подкласса TreeNode<T> в Bar класса случае, так как Bar наследуется от TreeNode<Foo> - так или иначе позволяет опускать клали SetParent и это упущение избежать недопустимое исключение броска с момента назначение во всех случаях является законным. Затраты на это больше не могут свободно использовать T во всех местах, как ранее использовалось, что приносит большую часть ценности CRTP.

Мой коллега считает себя новичком в языке/языке, пока он не может честно сказать, что он «использовал его в гневе»; то есть он знает язык достаточно хорошо, чтобы быть расстроенным, что нет ни одного способа выполнить то, что ему нужно, или что это так больно. Это очень хорошо может быть одним из таких случаев, поскольку здесь существуют ограничения и различия, которые отражают правду, что generics are not templates.

0

Когда вы работаете со ссылочными типами, и знаете, что ваш бросок по иерархии типов будет успешным (здесь нет настраиваемого кастинга), тогда нет необходимости на самом деле что-то делать. Значение ссылочного целого равно до и после броска, так почему бы просто не пропустить бросок?

Это означает, что вы можете написать этот презренный метод AddChild в CIL/MSIL. Коды операций с телом метода следующие:

ldarg.1 
ldarg.0 
stfld TreeNode<class T>::m_parent 
ret 

. NET не заботится о том, чтобы вы не произвели это значение. Кажется, что Джиттер заботится только о том, насколько размер хранилищ согласован, и он всегда для ссылок.

Загрузите расширение поддержки IL для Visual Studio (возможно, вам нужно открыть файл vsix и изменить поддерживаемую версию) и объявить метод C# как extern с атрибутом MethodImpl.ForwardRef. Затем просто повторно объявите класс в файле .il и добавьте требуемую реализацию метода, тело которой приведено выше.

Обратите внимание, что это также вручную включает метод SetParent в AddChild.