generic interface conversion question

G

Guest

Hi all,

I'm having trouble with a project that I've distilled down to the following
code. Unfortunately it won't compile and gives the following

error (caused by the call to GetHorses() in HorseFarm's GetAnimals() method):


Cannot implicitly convert type 'MyCollection<Horse>' to
'MyCollection<IAnimal>'


I tried creating an implicit conversion operator in the Horse class but the
compiler informed me that it was illegal to do so when the conversion was to
or from an interface.


I can't quite see why the the conversion doesn't occur automatically as the
Horse class implements IAnimal so you'd think a collection of

horses would be a collection of animals (I can after all return a single
Horse whenever an IAnimal is expected).

That being said, my real implementation is much more complex and I really
need to leverage the equivalent of a GetHorses() method within a HorseFarm's
GetAnimals() method.

Is there some type of design pattern for generics that can solve this?

Thanks,

Dave


--------- CODE START ---------------

public class MyCollection<T>
{
}

public interface IAnimal
{
}

public interface IFarm
{
MyCollection<IAnimal> GetAnimals();
}

public class Horse : IAnimal
{
}

public class HorseFarm : IFarm
{
MyCollection<Horse> horses = new MyCollection<Horse>();

public MyCollection<Horse> GetHorses()
{
return horses;
}

public MyCollection<IAnimal> GetAnimals()
{
return GetHorses();
}
}

--------- CODE END ---------------
 
J

John B

Dave said:
Hi all,

I'm having trouble with a project that I've distilled down to the following
code. Unfortunately it won't compile and gives the following

error (caused by the call to GetHorses() in HorseFarm's GetAnimals() method):


Cannot implicitly convert type 'MyCollection<Horse>' to
'MyCollection<IAnimal>'
<...>
Google c# covariance.
..Net (c#?) does not* support covariance and contravariance.

You should be able to define it as IAnimal[] GetAnimals() and call
ToArray() on MyCollection (presuming you're inheriting List<T>).

JB
*Except in some cases. Delegate return types, Array conversion...
 
B

Ben Voigt [C++ MVP]

I can't quite see why the the conversion doesn't occur automatically as
the
Horse class implements IAnimal so you'd think a collection of

horses would be a collection of animals (I can after all return a single
Horse whenever an IAnimal is expected).

No, because if it was a collection of animals you could add a pig...
 
G

Guest

Thank John, I am now digging through the following:

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

John B said:
Dave said:
Hi all,

I'm having trouble with a project that I've distilled down to the following
code. Unfortunately it won't compile and gives the following

error (caused by the call to GetHorses() in HorseFarm's GetAnimals() method):


Cannot implicitly convert type 'MyCollection<Horse>' to
'MyCollection<IAnimal>'
<...>
Google c# covariance.
..Net (c#?) does not* support covariance and contravariance.

You should be able to define it as IAnimal[] GetAnimals() and call
ToArray() on MyCollection (presuming you're inheriting List<T>).

JB
*Except in some cases. Delegate return types, Array conversion...
 
G

Guest

Hi Ben,

I understand that a collection of animals might contain pigs and other
animals but a collection of horses is definitely comprised solely of horses;
it therefore seems strange that a horse collection can not be used where an
animal collection is expected.

I've adapted my sample code a little (see below) to more closely model what
I'm trying to achieve with my collection that's based on WPF's
ObservableCollection<> (namely a way to reference the object which "contains"
the collection so I can navigate my way back up the logical object hierarchy
without tightly coupling myself to the presentation layer).

Also in the sample is how what I'm trying to does work with plain old arrays
but breaks as soon as generics are introduced.

Dave

using System.Collections.ObjectModel;

public class MyCollection<TContainer, TMember> : ObservableCollection<TMember>
{
public MyCollection(TContainer container)
{
Container = container;
}

TContainer Container { get; set; } // C# 3.0 automatic property
}

public interface IAnimal
{
}

public interface IFarm
{
IAnimal[] GetAnimalAray();
MyCollection<IFarm, IAnimal> GetAnimalCollection();
}

public class Horse : IAnimal
{
}

public class HorseFarm : IFarm
{
IAnimal[] horseArray;
MyCollection<IFarm, Horse> horseCollection;

public HorseFarm()
{
horseArray = new IAnimal[] { };
horseCollection = new MyCollection<IFarm, Horse>(this);
}

public IAnimal[] GetAnimalAray()
{
return horseArray;
}

public MyCollection<IFarm, IAnimal> GetAnimalCollection()
{
return horseCollection; // causes compilation error
}
}
 
J

Jon Skeet [C# MVP]

Dave Weeden said:
I understand that a collection of animals might contain pigs and other
animals but a collection of horses is definitely comprised solely of horses;
it therefore seems strange that a horse collection can not be used where an
animal collection is expected.

You should really read Eric Lippert's series of posts on variance:

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravar
iance/default.aspx

Also Rick Byers:

http://blogs.msdn.com/rmbyers/archive/2005/02/16/375079.aspx

Consider what you can do with a collection of animals: you can *add*
any animal to it. What would you expect to happen when you add a tiger
to your collection of horses? What about the other pieces of code which
still had a reference to it as a collection of horses?
 
G

Guest

Thanks, that's definite food for thought and is very likely the root of why
my model isn't quite right.

IIRC, the array returned by GetAnimalArray() is a copy so the replacing one
of the horse elements in that copy with a tiger would not cause a problem.

John B's post from above had also pointed me down the road towards Eric's
posts.

I'll definitely be checking them out :)

Thanks!
 
J

John B

Jon Skeet [C# MVP] wrote:
Consider what you can do with a collection of animals: you can *add*
any animal to it. What would you expect to happen when you add a tiger
to your collection of horses?

string[] strings = new string[] { "a", "b", "c", "d", "e", "f", "g" };
object[] objs = (object[])strings;
objs[0] = new object(); //oops

Ideally, not what happens when you do the above :)

<...>

JB
 
J

Jon Skeet [C# MVP]

John B said:
Jon Skeet [C# MVP] wrote:
Consider what you can do with a collection of animals: you can *add*
any animal to it. What would you expect to happen when you add a tiger
to your collection of horses?

string[] strings = new string[] { "a", "b", "c", "d", "e", "f", "g" };
object[] objs = (object[])strings;
objs[0] = new object(); //oops

Ideally, not what happens when you do the above :)

Exactly. Generics is meant to give compile-time type safety. Array
variance punts the type safety to execution time :(
 
B

Ben Voigt [C++ MVP]

Jon Skeet said:
John B said:
Jon Skeet [C# MVP] wrote:
Consider what you can do with a collection of animals: you can *add*
any animal to it. What would you expect to happen when you add a tiger
to your collection of horses?

string[] strings = new string[] { "a", "b", "c", "d", "e", "f", "g" };
object[] objs = (object[])strings;
objs[0] = new object(); //oops

Ideally, not what happens when you do the above :)

Exactly. Generics is meant to give compile-time type safety. Array
variance punts the type safety to execution time :(

I think array variance ought to require the /unsafe compiler switch.
 
J

Jon Skeet [C# MVP]

I think array variance ought to require the /unsafe compiler switch.

Hmm... it's a slightly different type of safety, I think - a separate
switch, perhaps. Or perhaps it should just have been disallowed in the
first place. Apparently it was included mostly to be compatible with
Java, bizarrely enough.

Jon
 
B

Ben Voigt [C++ MVP]

Jon Skeet said:
Hmm... it's a slightly different type of safety, I think - a separate
switch, perhaps. Or perhaps it should just have been disallowed in the
first place. Apparently it was included mostly to be compatible with
Java, bizarrely enough.

Jon

No, there's advantages of overloading the "/unsafe" switch such as running
afoul of all the code style guides out there that say "no /unsafe". Because
the people who can't get unsafe code right are not going to get array
variance right either.

Yes, I realize that array variance is permissible in a partial-trust
environment, because of the extra runtime type-checking inserted by the
compiler.

I guess we could use the "/theoreticallyincorrect" switch instead?
 
G

Guest

Thanks all of you guys for pointing me down the right road, I get it now.

It's nice to dive back into theory sometimes :)
 
J

Jon Skeet [C# MVP]

Ben Voigt said:
No, there's advantages of overloading the "/unsafe" switch such as running
afoul of all the code style guides out there that say "no /unsafe". Because
the people who can't get unsafe code right are not going to get array
variance right either.

Ooh, I don't know about that. I personally steer well clear of unsafe
code, and don't want to think about pinning etc - but I'm comfortable
enough getting variance right.
Yes, I realize that array variance is permissible in a partial-trust
environment, because of the extra runtime type-checking inserted by the
compiler.

Do you mean the JIT compiler? It's not done by the C# compiler.
I guess we could use the "/theoreticallyincorrect" switch instead?

Well, I'd go for something like: /arrayvariance={warn,error,allow} - or
just make it a warning that can be controlled in the same way as other
warnings.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top