2012-02-02 13 views
5

he estado ocupado con C# 4.0 genéricos, y ahora básicamente quiero hacer algo como esto: clase TreeC# - ¿Hay alguna forma de lanzar una colección genérica?

public abstract class GenericTree<T> : Tree 
    where T : Fruit 
{ 
    public Tree(IFruitCollection<T> fruits) 
     : base(fruits) { } 
} 

La base es el siguiente:

public abstract class Tree 
{ 
    private IFruitCollection<Fruit> fruits; 

    public IFruitCollection<Fruit> GetFruits 
    { 
     get { return fruits; } 
    } 

    public Tree(IFruitCollection<Fruit> fruits) 
    { 
     this.fruits = fruits; 
    } 
} 

Aquí está mi primer problema . El constructor de GenericTree no puede convertir la colección genérica en una colección de frutas. También tengo una implementación de GenericTree:

public class AppleTree : GenericTree<Apple> 
{ 
    public AppleTree() 
     : base(new FruitCollection<Apple>) { } 
} 

Aquí está mi segundo problema. Cuando agrego fruta a una instancia de AppleTree, utilizando myAppleTree.GetFruits.Add (...), no estoy restringido solo a las manzanas. Puedo agregar todo tipo de fruta. No quiero esto

Me trataron de resolver ese problema mediante la adición de esto a la GenericTree:

new public IFruitCollection<T> GetFruits 
{ 
    get { return base.GetFruits as IFruitCollection<T>; } 
} 

Pero esto no es posible tampoco. De alguna manera, siempre devuelve nulo. Podría ser posible que esto se resuelva, cuando se resuelva mi primer problema.

interfaz El IFruitCollection se ve así:

public interface IFruitCollection<T> : ICollection<T> 
    where T : Fruit { ... } 

Y la clase FruitCollection es una implementación sencilla de la clase de colección. Ah, y por supuesto, la clase Apple extiende la clase Fruit.

La solución es hacer que la interfaz IFruitCollection sea compatible con la covarianza y la contravarianza. ¿Pero cómo logro esto? Las palabras clave del parámetro "in" o "out" no son posibles porque la interfaz ICollection no lo permite.

¡Muchas gracias por su ayuda!

+0

Tenga en cuenta que esto puede ser fácilmente resuelto mediante la fusión del árbol y la clase GenericTree en una sola. Pero recuerda que esto es solo un ejemplo de cuál es mi problema. Mi propia aplicación es un poco más compleja. –

+0

Como implica, la solución es hacer Tree genérico. También implica que esto no es posible. Por qué no? – phoog

+0

En algún momento, necesito una lista de árboles. Y una lista de Tree no lo hace, porque no me permite agregar instancias de árbol . Por supuesto, indicará que no existe una referencia implícita entre Tree y Tree . –

Respuesta

1

Creo que encontré mi solución, mientras estaba probando la solución de phoog. Gracias, Phoo!

Al principio no le di a GenericTree su propia FruitCollection. Mosty porque la clase base a menudo recorre su propia colección, por ejemplo, para actualizar las frutas. Esto dio como resultado que las frutas no se actualizaran, porque se agregaron a la colección de Generic Tree, no a la colección de base.

Pero finalmente me di cuenta de que el lanzamiento de esas colecciones nunca va a funcionar tampoco.

Ahora he creado otra clase FruitCollection que agrega y quita componentes automáticamente a la base FruitCollection, y viceversa. ¡Esta solución funciona genial para mí!

public FruitCollection<T, U> : FruitCollection<T> 
    where T : U 
    where U : Fruit 
{ 
    private FruitCollection<U> baseCollection; 

    public FruitCollection(FruitCollection<U> baseCollection) 
     : base() 
    { 
     this.baseCollection = baseCollection; 

     // here I added code that throws events whenever the collection is changed 
     // I used those events to add/remove fruit to the base collection, and vice versa 
     // see the link below. 
     ... 
    } 

    ... 
} 

public class GenericTree<T>: Tree 
    where T : Fruit 
{ 
    private FruitCollection<T> fruits; 

    // use the new keyword to hide the base collection 
    new public FruiCollection<T> Fruits 
    { 
     get { return fruits; } 
    } 

    public GenericTree() 
     : base() 
    { 
     // after hiding the base collection, use base.Fruits to get it 
     fruits = new FruitCollection<T, Fruit>(base.Fruits); 
    } 
} 

Esto me ayudó: How to handle add to list event?

2

En respuesta a tu comentario:

En algún momento, necesito una lista de árboles. Y una lista de Tree<Fruit> no lo hace, porque no me permite agregar instancias Tree<Banana>. Por supuesto, indicará que no hay referencia implícita entre Tree<Fruit> y Tree<Banana>.

El problema básico es que desea tener una colección, la lista de árboles, que (indirectamente) contiene objetos similares de diferentes tipos. En aras de la desambiguación de esa colección del Tree (que también es una colección, de Fruit), vamos a llamarlo Orchard.

Orchard - (contiene) -> árbol - (contiene) -> Frutas

Si comete un no genérico Tree, elementos de la Orchard 's podrían ser todos de ese tipo. Pero como habrás notado, esto significa que terminas con el problema de que los árboles no son seguros, y puedes poner un plátano en un manzano. Debería resolver ese problema con las comprobaciones del tipo de tiempo de ejecución en la implementación Tree.

Alternativamente, podría hacer una clase genérica Tree<T> where T : Fruit, por lo que tiene seguridad de tipo con respecto a la subclase de Fruit contenida en el árbol. Esto significa que el Orchard contendrá objetos de diferentes tipos, requiriendo de nuevo la necesidad de verificaciones del tipo de tiempo de ejecución.

(usted podría hacer un huerto con la seguridad de tipo estático, al declarar un descriptor de acceso independiente para cada tipo:

class Tree { } 
class Tree<T> : Tree { } 
class Trees : IEnumerable<Tree> 
{ 
    Tree<Banana> _bananaTree; 
    Tree<Apple> _appleTree; 
    //...etc. 

    Tree<Banana> GetBananaTree() { return _bananaTree; } 
    Tree<Apple> GetBananaTree() { return _appleTree; } 
    //...etc. 

    public IEnumerator<Tree> GetEnumerator() 
    { 
     yield return _bananaTree; 
     yield return _appleTree; 
     //...etc. 
    } 
} 

Pero eso no es probablemente lo que usted quiere, por lo que es necesario tener un elenco algún lugar.)

asumo que usted prefiere tener el elenco en el Orchard que en el Tree, en cuyo caso yo sugeriría algo como esto para el enfoque subyacente:

IDictionary<Type, object> orchard = new Dictionary<Type, object>(); 

//to retrieve the tree 
Tree<Banana> bananaTree = (Tree<Banana>)orchard[typeof(Banana)]; 

(Por supuesto, se puede usar un tipo más específico que object, como Tree o ICollection o IEnumerable.)

yo iría más lejos y encapsular esa lógica en una clase de Orchard, lo que podría proporcionar una menor la sintaxis detallada realizando el reparto en el descriptor de acceso:

var bananaTree = orchard.GetTree<Banana>(); 

donde:

public class Orchard 
{ 
    private IDictionary<Type, object> _trees; 

    //... 

    public Tree<T> GetTree<T>() 
    { 
     return (Tree<T>)_trees[typeof(T)]; 
    } 
} 

En última instancia, este es un gran ejemplo de por qué los parámetros de tipo co y contravariantes en las interfaces deben restringirse a las posiciones de salida y entrada, respectivamente. Para los tipos que se utilizan como entrada y salida, terminas necesitando verificaciones del tipo de tiempo de ejecución.

+0

Gracias por su respuesta.Ayer me di cuenta de que tenía que deshacerme de la clase Tree no genérica, y prácticamente me lo confirmaste. Su clase de Orchard parece una gran solución. Aunque me temo que esto no será aplicable para mi aplicación "real", lo intentaré mañana. –

+0

Su solución alternativa funcionó para mí, pero encontré otra solución mientras usaba la suya. Eche un vistazo a mi propia respuesta. ¡Gracias por tu ayuda! –

+0

@Rudey es una solución interesante, y funcionará bien si no tienes toneladas de objetos. Tengo un proyecto donde las colecciones contienen millones de objetos, y la carga de almacenamiento de tener * dos * colecciones para referencias de tipos diferentes sería inaceptable allí. – phoog

Cuestiones relacionadas