2

I have 2 classes: Parent and Child:Parent.

When i do next:

IMyRepository<Child> _childRepository=new MyRepository<Child>();
IMyRepository<Parent> _repository=childRepository;

i get error "Сan't convert source type to target type". Tell me please why this code is not working.

Frank59
  • 3,141
  • 4
  • 31
  • 53
  • 4
    Because we want to prevent `_repository.Add(new SOmeOtherChildThatDerivesFromParentButIsnt_Child())` from compiling. – Damien_The_Unbeliever Sep 18 '13 at 09:19
  • 1
    knittl's answer mentions covariance and contravariance - here's some great background reading on the subject - http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx – NDJ Sep 18 '13 at 09:24

5 Answers5

5

Because, then you would be able to insert a new AnotherDifferentChild() – this can't possibly exist in a IList<Child>. If you want to learn more about the details, look up articles on Covariance, Contravariance, and Invariance.

If you want to create a new list, holding references of type Parent, you can use the Cast<T>() method from LINQ:

IList<Parent> parentList = childList.Cast<Parent>().ToList();
knittl
  • 246,190
  • 53
  • 318
  • 364
  • +1 for bringing up C# 4.0's Covariance & Contravariance feature. I think the rest of your answer is irrelevant though. – Mr. Smith Sep 18 '13 at 09:27
  • @Mr.Smith: .NET 4 offers handling of cases involving co(ntra)variance, but they are a "problem" (so to say) in every OOP system. – knittl Sep 18 '13 at 09:36
1

I'm not too clued up on casting, but I don't think generics would do an implicit cast to the parent type.

but adding

childRepository.Cast<Parent>()

Should make it possible although you may have to make an extension for IEnumerable<T> that would create a new IMyRepository<T>

ywm
  • 1,107
  • 10
  • 14
1

Try this

List<Parent> listOfParent = new List<Child>().Cast<Parent>().ToList();

Or

List<Parent> listOfParent = new List<Child>().ConvertAll(x => (Parent)x);
Prasad Kanaparthi
  • 6,423
  • 4
  • 35
  • 62
1

If we use slightly different class names, the reason you're not allowed to do this will become clear.

Consider this class hierarchy:

public class Mammal
{
}

public class Cat: Mammal
{
    public void Meow(){}
}

Now assume you have the following list:

IList<Cat> cats = new List<Cat>{ new Cat() }

cats[0].Meow(); // All is fine.

Now let's assume you can assign cats to an IList<Mammal>:

IList<Mammal> mammals = cats; // Not allowed, but pretend it is.

mammals.Add(new Mammal()); 

// Because 'mammals' is referencing 'cats', then adding an element to 'mammals'
// will affect 'cats' too - they are both the same list.
// So now cats has two elements; the first is a Cat and the second is a Mammal.

// So now what happens when we do this?

cats[1].Meow(); // Ask a Mammal to Meow(). Ooopsie!
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
0

Imagine what would happen if you could do such a thing. You could write code like the following:

    IList<Child> children = new List<Child>();
    IList<Parent> parents = children;
    parents.Add(new Parent());

Note that parents is still a reference to the same object as children, however we have managed to add an object that is not an instance of Child to a IList<Child>!

As another answer has mentioned, this is all to do with the issue of covariance (closely related to an area of mathematics known as Category Theory - interesting if you get a chance to look at it).

Essentially, if we write T-> S, for two types where S is polymorphic to T, say Parent -> Child, then a generic is covariant if it preserves this relationship. For example IEnumerable is a covariant because IEnumerable<Object> -> IEnumerable<String>. The compiler knows this, as you can cast an IEnumerable<String> to an IEnumerable<Object>.

Lawrence
  • 3,287
  • 19
  • 32