tags:

views:

423

answers:

4

the title says it all ...

interface IFolderOrItem<TFolderOrItem> where TFolderOrItem : FolderOrItem {}

abstract class FolderOrItem {}

class Folder : FolderOrItem {}

abstract class Item : FolderOrItem {}

class Document : Item {}

now i'm trying to do sth like this:

class Something
{
    IFolderItemOrItem<Item> SelectedItem { get; set; }
    void SomeMagicMethod()
    {
     this.SelectedItem = (IFolderOrItem<Item>)GetMagicDocument();
     // bad bad bad ... ??
    }
    IFolderOrItem<Document> GetMagicDocument()
    {
     return someMagicDocument; // which is of type IFolderOrItem<Document>
    }
}

is there any possibility to get this working?

+6  A: 

As far as the compiler is concerened, IFolderOrItem<Document> & IFolderOrItem<Item> are two completely different types.

Document may inherit Item, but IFolderOrItem<Document> does not inherit IFolderOrItem<Item>

I'm relying on Marc or Jon to post links to the relevant portions of the C# spec.

Binary Worrier
lol - but I believe this is one of those things that aren't in the C# 3.0 spec simply because it isn't a consideration. There should be something in the 4.0 spec, though.
Marc Gravell
Tell a lie... updating ;-p
Marc Gravell
+3  A: 

The problem is that a cast does not work on the generic arguments, but on the class as a whole. Document inherits from Item, true, but IFolderOrItem< Document> does not inherit from IFolderOrItem< Item>, nor is related with it in any way.

Gorpik
+12  A: 

If I read it correctly... then the problem is that just because Foo : Bar, that does not mean that ISomething<Foo> : ISomething<Bar>...

In some cases, variance in C# 4.0 may be an option. Alternatively, there are sometimes things you can do with generic methods (not sure it will help here, though).


The closest you can do in C# 3.0 (and below) is probably a non-generic base interface:

interface IFolderOrItem {}
interface IFolderOrItem<TFolderOrItem> : IFolderOrItem
    where TFolderOrItem : FolderOrItem { }

commonly, the base-interface would have, for example, a Type ItemType {get;} to indicate the real type under consideration. Then usage:

IFolderOrItem SelectedItem { get; set; }
...
public void SomeMagicMethod()
{
    this.SelectedItem = GetMagicDocument(); // no cast needed
    // not **so** bad
}


From the spec, this relates to §25.5.6 (ECMA 334 v4):

25.5.6 Conversions

Constructed types follow the same conversion rules (§13) as do non-generic types. When applying these rules, the base classes and interfaces of constructed types shall be determined as described in §25.5.3.

No special conversions exist between constructed reference types other than those described in §13. In particular, unlike array types, constructed reference types do not permit co-variant conversions (§19.5). This means that a type List<B> has no conversion (either implicit or explicit) to List<A> even if B is derived from A. Likewise, no conversion exists from List<B> to List<object>.

[Note: The rationale for this is simple: if a conversion to List<A> is permitted, then apparently, one can store values of type A into the list. However, this would break the invariant that every object in a list of type List<B> is always a value of type B, or else unexpected failures can occur when assigning into collection classes. end note]

The same applies to interfaces. This changes a bit in C# 4.0, but only in some cases.

Marc Gravell
can you give me some hint, to get this working?
Andreas Niedermair
Tricky... it *isn't* an IFolderOrItem<Item> - so unless you wrap it, is isn't going to work. Depending on what the members in the interface are, it *might* work (with an extra modifier) in 4.0. Other than that... perhaps declare a non-generic interface...
Marc Gravell
+1: I knew you'd find something :)
Binary Worrier
your solution of generic free interface combined with an abstract class (generic free one + generic one) just hits the nail ... wow! thank you!
Andreas Niedermair
+2  A: 

An example to understand why it works this way :

Suppose IFolderOrItem exposes a method, for example, void Add(T element).

Your implementation for IFolderOrItem will suppose the parameter is a Document.

But of you cast your IFolderOrItem as IFolderItemOrItem, then someone could call the method Create(T) where T is supposed to be an Item.

The cast from Item to Document is invalid, since an Item is not a Document.

The only way for you to do this is to create a non-generic version of the interface, allowing objects as parameters, the check the type of the object in your implementations.

Styx31