2015-09-11 8 views
7

Targeting .net 4.0, sto cercando di costruire le seguenti classi:Fusioni non valido di tipo Constrained C# Generico

public class ConfigurationElementCollection<TElement, TParent> 
    where TElement : ParentedConfigElement<TParent>, new() 
    where TParent : class 
{ 
    public TParent ParentElement { get; set; } 

    protected ConfigurationElement CreateNewElement() 
    { 
     //************************************************** 
     //COMPILER GIVES TYPE CONVERSION ERROR ON THIS LINE! 
     //************************************************** 
     return new TElement { ParentCollection = this }; 
    } 
} 

public class ParentedConfigElement<TParent> : ConfigurationElement where TParent : class 
{ 
    internal ConfigurationElementCollection<ParentedConfigElement<TParent>, TParent> 
     ParentCollection { get; set; } 

    protected TParent Parent 
    { 
     get 
     { 
      return ParentCollection != null ? ParentCollection.ParentElement : null; 
     } 
    } 
} 

Come il codice di commento indica sopra, il compilatore dà un errore:

Cannot implicitly convert type 'Shared.Configuration.ConfigurationElementCollection<TElement, TParent>' to 'Shared.Configuration.ConfigurationElementCollection<Shared.Configuration.ParentedConfigElement<TParent>,TParent>

Non mi aspetto questo errore, perché il compilatore sa anche che TElement è un Shared.Configuration.ParentedConfigElement<TParent> a causa del vincolo di tipo generico che ho specificato.

Eppure, ho dato io getterò esplicitamente il tipo per superare questo problema:

(ConfigurationElementCollection<ParentedConfigElement<TParent>,TParent>) this; 

Purtroppo, ottengo lo stesso errore del compilatore. Perché sta succedendo? Che cosa ho fatto di sbagliato? E senza ricorrere ai tipi dynamic, cosa posso fare per risolvere questo problema?

risposta

2

Il tuo problema è che avete un tipo CEC<A, B> e si cerca di assegnarlo a una proprietà di tipo CEC<C<A>,B> che non si può fare, più o meno allo stesso modo in cui List<string> non possono essere assegnati a memorizzazione di tipo List<object> anche se string deriva da object.

Una soluzione pulita senza l'utilizzo di operatori impliciti o dinamico è quello di utilizzare un'interfaccia:

public interface IConfigurationElementCollection<TParentedConfig, TParent> 
    where TParentedConfig : ParentedConfigElement<TParent> 
    where TParent : class 
{ 
    TParent ParentElement { get; } 
} 

public class ConfigurationElementCollection<TElement, TParent> : IConfigurationElementCollection<ParentedConfigElement<TParent>, TParent> 
    where TElement : ParentedConfigElement<TParent>, new() 
    where TParent : class 
{ 
    public TParent ParentElement { get; set; } 

    protected ConfigurationElement CreateNewElement() 
    { 
     //************************************************** 
     //COMPILER NO LONGER GIVES TYPE CONVERSION ERROR 
     //BECAUSE this IMPLEMENTS THE EXPECTED INTERFACE! 
     //************************************************** 
     return new TElement { ParentCollection = this }; 
    } 
} 

public class ParentedConfigElement<TParent> : ConfigurationElement where TParent : class 
{ 
    internal IConfigurationElementCollection<ParentedConfigElement<TParent>, TParent> 
     ParentCollection { get; set; } 

    protected TParent Parent 
    { 
     get 
     { 
      return ParentCollection != null ? ParentCollection.ParentElement : null; 
     } 
    } 
} 

Dal momento che la struttura in ParentedConfigElement è interno, è possibile anche fare l'interfaccia interna per evitare di esporre questo dettaglio di implementazione a tutti i consumatori , se quel genere di cose è una preoccupazione per te.

+0

È odioso che il vincolo generico su CEC connota esattamente la stessa cosa dell'interfaccia che hai fornito ... e tuttavia il vincolo generico sembra essere ignorato. Tuttavia, sono molto felice che tu abbia trovato un modo per farlo funzionare senza usare un tipo 'dinamico '. –

+0

Usare la composizione è molto meglio e il modo giusto per farlo :). Rimozione della mia risposta. –

+0

Ma il vincolo generico non dice esattamente la stessa cosa dell'interfaccia, dice solo che il primo argomento di tipo eredita da "ParentedConfigElement ". Ciò significa che 'TElement' è assegnabile a' ParentedConfigElement 'ma quell'ereditarietà non si estende ai parametri di tipo di tipi generici. – Erik

4

Basato su https://stackoverflow.com/a/6529618/5071902

protected ConfigurationElement CreateNewElement() 
{ 
    return (TElement)Activator.CreateInstance(typeof(TElement), this); 
} 

You will need a constructor with this signature, setting the ParentCollection property.

Puoi provare a utilizzare riflessione troppo. Date un'occhiata a questa risposta https://stackoverflow.com/a/6529622/5071902

+0

Activator.CreateInstance è necessario solo quando non esiste un costruttore predefinito nell'oggetto di destinazione. Nel mio esempio, ho effettivamente un costruttore predefinito. È solo una proprietà che sto inizializzando ... non un parametro costruttore. –