0

Suppose I have following code:

public class CBase: AbstractC,IRenderable
{
 //code here
}

public class CBaseGroup
{
 private List<IRenderable> CCollection;

 public CBaseGroup(List<IRenderable> c)
 {
   CCollection=c;
 }
}

public class CGroup:CBaseGroup
{
 public CGroup(List<CBase> c):base(c) 
 //here fails because cannot convert List<CBase> to List<IRenderable>
 {
 }

}

Why it does not compile?

Please feel free to suggest a nappropriate title.

From Matthew Scharley answer I found that the code should look like:

  public class CGroup:CBaseGroup
    {
     public CGroup(List<CBase> c):base(c.Cast<IRenderable>().ToList()) 
      // cast to list since I'm not using IEnumerable
     {
     }

    }

This at least satisfies compiler.

Eugeniu Torica
  • 7,484
  • 12
  • 47
  • 62
  • 2
    Covariance and contravariance. Is there one good answer we can always point back to? – Rex M Aug 18 '09 at 22:23
  • Yes, that is the only/best way to do it in C# 3.5. Hopefully we get a better solution with C# 4.0 (I havn't looked too closely at it, just what I hear around here). – Matthew Scharley Aug 18 '09 at 22:43
  • No, we won't get a better solution in C# 4.0, nor in any other language which is typesafe. See http://stackoverflow.com/questions/981570/c-no-implict-conversion-from-classchild-to-classbase – Pavel Minaev Aug 18 '09 at 23:44

4 Answers4

4

To get the effect you want you'd need to do something like

public class CBaseGroup<T> where T : IRenderable
{
 private List<T> CCollection;

 public CBaseGroup(List<T> c)
 {
   CCollection=c;
 }
}
public class CGroup:CBaseGroup<CBase>
{
 public CGroup(List<CBase> c):base(c) 
 {
 }
}

to inject the specific type of IRenderable into the base class.

Steve Gilham
  • 11,237
  • 3
  • 31
  • 37
2

C# 3.5 doesn't support contravarience and covarience. I believe I've heard that C# 4.0 will (someone correct me if I'm wrong.)

For a more extended discussion on this, have a look at this other question.

Community
  • 1
  • 1
Matthew Scharley
  • 127,823
  • 52
  • 194
  • 222
  • Yes C# 4.0 does include support for contra/covariance – Simon Fox Aug 18 '09 at 22:26
  • Since `List` is inherently invariant, variance support in the language won't help you one bit here. It's just not typesafe to do that, period. – Pavel Minaev Aug 18 '09 at 23:42
  • There are many cases where casting a `List` to a `List` (or perhaps `List`!) would be incredibly useful. I'd argue that you should be able to, since if you created a `List` in the first place, you've still got the same issues anyway, there's no **new** problems introduced by making the `List` class variant. – Matthew Scharley Aug 18 '09 at 23:52
0

Well, on line 1, you need to include the parent class before any interfaces

public class CBase: AbstractC, IRenderable
Frank Schwieterman
  • 24,142
  • 15
  • 92
  • 130
  • Pretty sure order makes no difference here. Stylistically, you're probably right, but interfaces (should) always start with `'I'`, so it really shouldn't be too much of an issue. – Matthew Scharley Aug 18 '09 at 22:27
0

This is a covariance problem as C# 3.5 does not support this kind of generic type conversion.There will be more support for generic co- and contra-variance in C# 4.0.

The below type of conversion works as we expect and have done many times

IRenderable renderable = cbaseObject as IRenderable;

A more specific type, cbaseObject, is being converted to a more general type, IRenderable, in the class hierarchy. However, the following which is similar to your case

List<CBase> listOfCBaseObjects = new List<CBase>();
IEnumerable<IRenderable> renderables = listOfCBaseObject as IEnumerable<IRenderable>;

will not work because it is not supported in current C# releases.

However, support for covariance of arrays that hold reference types has been there since C# 1.0. See Eric Lippert's blog port. So if you do the following, the conversion will work:

CBase [] cbaseObjects = new CBase[] { new CBase(),new CBase()};
IRenderable [] renderables = cbaseObjects as IRenderables[];

So alternatively, if you want, you can change CBase and CBaseGroup constructors to take an array instead of a list. So that you can do the following:

//constructor
public CBaseGroup(IRenderable[] c)

//constructor
public CGroup(CBase[] c):base(c as IRenderable[]) 

and this should work.

Mehmet Aras
  • 5,284
  • 1
  • 25
  • 32
  • This is because it's not really covarience at all, it's a simple cast of each element of the array. This is something the compiler can handle, as opposed to a generic parameter which can be used for anything. – Matthew Scharley Aug 18 '09 at 23:55
  • I believe it is covariance at least by definition. The conversion operation between two arrays is allowed such that what you end up with still preserves the ordering of types contained. See the array covariance section in the C# language spec http://msdn.microsoft.com/en-us/library/aa664572(VS.71).aspx. It may be implemented as you described but that does not make it not real covariant. – Mehmet Aras Aug 19 '09 at 01:04