2012-02-20 28 views
5

Attualmente sto lavorando a un modo semplice per implementare una struttura ad albero intrusiva in C#. Essendo principalmente un programmatore C++, ho immediatamente voluto utilizzare CRTP. Qui è il mio codice:C# - Struttura ad albero intrusiva, utilizzando 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; 
} 

Questo funziona, ma ... non riesco a capire il motivo per cui devo lanciare quando si chiama a_node.SetParent ((T) questo), come sto usando generico restrizione tipo .. . C# getto ha un costo, e vorrei non diffondere questo cast in ogni implementazione collezione invadente ...

+0

Mi sono permesso di semplificare il tuo esempio. Si prega di annullare se non si è d'accordo. – usr

+1

Per essere onesti, questo sembra un intelligente headf ** k. Cosa c'è di sbagliato nei modi più tradizionali di rappresentare gli alberi usando invece la composizione? Cosa ti compra? Spero che non sembri troppo antagonista. Sono solo curioso. – spender

+0

@spender dimezza il numero di allocazioni e riduce il numero di riferimenti indiretti che è necessario seguire. Quindi nel codice ad alte prestazioni potrebbe essere un compromesso ragionevole. Per gli alberi più piccoli è probabilmente una cattiva idea. – CodesInChaos

risposta

3

questo è almeno di tipo TreeNode. Potrebbe essere derivato o potrebbe essere esattamente TreeNode. SetParent si aspetta un T. Ma T può essere un tipo diverso da questo. Sappiamo che questo e T derivano entrambi da TreeNode ma possono essere di diverso tipo.

Esempio:

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

Nessuno garantito che T e il tipo di this sono gli stessi. Possono anche essere sottoclassi non correlate di TreeNode.

Si aspetta che si utilizzi nel modello di modello curiosamente ricorrente, ma i vincoli generici non possono esprimerlo.

Un'implementazione stupida potrebbe essere definita come StupidNode:TreeNode<OtherNode>.

0

Il problema è con questa linea:

TreeNode<T> where T : TreeNode<T> 

T essendo un TreeNode è una definizione ricorsiva non può essere determinato precompilarli o anche controllato staticamente. Non utilizzare template, o se hai bisogno di refactoring & separare il nodo dal payload (cioè i dati nodo dal nodo stesso.)

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) 
    { 
    } 
} 

Anche io non sono sicuro perché si sta chiamando a_node .SetParent (questo). Sembra che AddChild sia più correttamente denominato SetParent, perché stai impostando questa istanza come genitore di a_node. Potrebbe essere un algoritmo esoterico con cui non ho familiarità, altrimenti non sembra giusto.

+0

Mentre le raccolte basate sulla composizione sono di solito un'idea migliore, le raccolte intrusive hanno il loro posto. Il 'TreeNode dove il vincolo di T: TreeNode ' ha senso in questo contesto. Può essere soddisfatto dal modello di modello curiosamente ricorrente. Anche l'implementazione di 'AddChild' mi sembra soddisfacente. In un albero implementate "SetParent" o "AddChild" in modo esplicito, quindi fate in modo che l'altro chiami quello che avete implementato. – CodesInChaos

0

considerare che cosa succede se ci si discosta da CRTP convenzione scrivendo ...

public class Foo : TreeNode<Foo> 
{ 
} 

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

... e quindi chiamare il codice precedente come segue:

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

La chiamata AddChild genera InvalidCastException dicendo Unable to cast object of type 'Bar' to type 'Foo'.

Per quanto riguarda l'idioma CRTP - è solo convenzione che richiede che il tipo generico sia lo stesso del dichiar tipo. La lingua deve supportare gli altri casi in cui la convenzione CRTP non viene seguita. Eric Lippert ha scritto un ottimo post sul blog su questo argomento, che ha collegato da questo altro crtp via c# answer.

Tutto ciò detto, se si cambia l'attuazione a questa ...

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; 
} 

... il codice di cui sopra, che in precedenza ha gettato il InvalidCastException ora funziona.La modifica rende m_Parent un tipo di TreeNode<T>; rendendo this sia il tipo di T come nel caso Foo class' o una sottoclasse di TreeNode<T> nel caso Bar classe dal Bar eredita da TreeNode<Foo> - in entrambi i casi ci consente di omettere il cast in SetParent e tale omissione evitare l'eccezione getto valido in quanto la l'assegnazione è legale in tutti i casi. Il costo di questa operazione non è più possibile utilizzare liberamente T in tutti i luoghi poiché era stato precedentemente utilizzato, il che sacrifica gran parte del valore di CRTP.

Un mio collega/amico si considera un novizio di una lingua/lingua fino a quando non può onestamente dire di essere "usato con rabbia"; cioè, conosce la lingua abbastanza bene da essere frustrato che non c'è modo di realizzare ciò di cui ha bisogno o che farlo è doloroso. Questo molto bene potrebbe essere uno di quei casi, in quanto vi sono limiti e differenze qui che fanno eco alla verità che è generics are not templates.

0

Quando si lavora con i tipi di riferimento e si sa che il cast lungo la gerarchia dei tipi avrà esito positivo (nessun casting personalizzato qui), non è necessario eseguire alcun cast effettivo. Il valore del numero intero di riferimento è lo stesso prima e dopo il cast, quindi perché non saltare il cast?

Ciò significa che è possibile scrivere questo metodo AddChild disprezzato in CIL/MSIL. I codici operativi metodo del corpo sono i seguenti:

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

.NET non si cura affatto che non è stato il cast del valore. Il Jitter sembra preoccuparsi solo delle dimensioni dei negozi che sono coerenti, che sono sempre per riferimenti.

Caricare l'estensione di supporto IL per Visual Studio (potrebbe essere necessario aprire il file vsix e modificare la versione supportata) e dichiarare il metodo C# come extern con l'attributo MethodImpl.ForwardRef. Quindi basta dichiarare nuovamente la classe in un file .il e aggiungere l'implementazione del metodo di cui si ha bisogno, il cui corpo è fornito sopra.

Si noti che questo allinea automaticamente il metodo SetParent in AddChild.

Problemi correlati