Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# - Is there some way to cast a generic collection?

I've been busy with C# 4.0 generics, and now I basically want to do something like this:

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

The base Tree class looks like this:

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

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

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

Here is my first problem. The constructor of GenericTree can't cast the the generic collection to a fruit collection. I've also got an implementation of the GenericTree:

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

Here's my second problem. When I add fruit to an instance of AppleTree, using myAppleTree.GetFruits.Add(...), I am not restricted to apples only. I am allowed to add all kinds of fruit. I don't want this.

I tried to solve that problem by adding this to the GenericTree:

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

But this is not possible either. It somehow always returns null. It could be possible that this will be solved, when my first problem is solved.

The IFruitCollection interface looks like this:

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

And the FruitCollection class is a simple implementation of the Collection class. Oh, and of course, the Apple class extends the Fruit class.

The solution is to make the IFruitCollection interface compatible with both covariance and contravariance. But how do I achieve this? The "in" or "out" parameter keywords are not possible, because the ICollection interface doesn't allow it.

Thanks a lot for your help!

like image 642
Rudey Avatar asked Feb 02 '12 16:02

Rudey


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.


1 Answers

In response to your comment:

At some point, I need a list of Trees. And a list of Tree<Fruit> doesn't do it, because doesn't allow me to add Tree<Banana> instances. It will than of course say that there is no implicit reference between Tree<Fruit> and Tree<Banana>.

The basic problem is that you want to have a collection -- the list of trees -- that (indirectly) contains similar objects of different types. For the sake of disambiguating that collection from the Tree (which is also a collection, of Fruit), let's call it an Orchard.

Orchard -(contains)-> Tree -(contains)-> Fruit

If you make a non-generic Tree, the Orchard's elements could all be of that type. But as you noticed, this means that you end up with the problem that the trees are not type safe, and you can put a banana in an apple tree. You would have to solve that problem with runtime type checks in the Tree implementation.

Alternatively, you could make a generic Tree<T> where T : Fruit class, so you have type safety with respect to the subclass of Fruit contained in the tree. This means that the Orchard will contain objects of different types, again requiring the need for runtime type checks.

(You could make an Orchard with static type safety by declaring a separate accessor for each type:

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

But that is probably not what you want, so you need to have a cast somewhere.)

I assume that you'd rather have the cast in the Orchard than in the Tree, in which case I would suggest something like this for the underlying approach:

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

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

(Of course, you could use a more specific type than object, like Tree, or ICollection, or IEnumerable.)

I would go further and encapsulate that logic in an Orchard class, which could provide a less verbose syntax by performing the cast in the accessor:

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

where:

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

    //...

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

Ultimately, this is a great example of why co- and contravariant type parameters in interfaces must be restricted to output and input positions, respectively. For types that are used both as input and output, you wind up needing runtime type checks.

like image 169
phoog Avatar answered Sep 24 '22 02:09

phoog