youwhut
youwhut

Reputation: 948

Confusion - implemented Interface needs casting?

I have an Entity class which Implements IWeightable:

Public Interface IWeightable

    Property WeightState As WeightState

End Interface

I have a WeightCalculator class:

Public Class WeightsCalculator

    Public Sub New(...)
        ...
    End Sub

    Public Sub Calculate(ByVal entites As IList(Of IWeightable))
        ...
    End Sub

End Class

Following the process:

  1. Instantiate collection of Entity Dim entites As New List(Of Entity)
  2. Instantiate WeightsCalculator Dim wc As New WeightsCalculator(...)

Why can I not do wc.Calculate(entities)? I receive:

Unable to cast object of type 'System.Collections.Generic.List1[mynameSpace.Entity]' to type 'System.Collections.Generic.IList1[myNamespace.IWeightable]'.

If Entity implements IWeightable why is this not possible?

Upvotes: 3

Views: 222

Answers (2)

Jon Skeet
Jon Skeet

Reputation: 1501113

A List(Of Entity) isn't an IList(Of IWeightable). Consider this code (where OtherWeightable implements IWeightable):

Dim list As IList(Of IWeightable) = New List(Of Entity)
list.Add(new OtherWeightable)

The second line as to compile - there's nothing suspicious about it - but you don't want an OtherWeightable element in a List(Of Entity).

.NET 4 has a partial solution to this in the form of generic variance. If your Calculate method only iterates over entities, you could change the signature to this:

Public Sub Calculate(ByVal entites As IEnumerable(Of IWeightable))

Although IList(Of T) is invariant, IEnumerable(Of T) is covariant in T, because the API only ever allows values of type T to be returned by it - there are no parameters of type T in methods of IEnumerable(Of T). So there's a conversion from List(Entity) to IEnumerable(Of IWeightable).

Generic variance is a hairy topic - at NDC 2010 I gave a presentation on it which you may find useful. You can watch it at the NDC 2010 videos page. (Search for "variance.")

If you're using .NET 3.5 or earlier, Konrad's suggestion of making Calculate generic is a fine choice.

Upvotes: 6

Konrad Rudolph
Konrad Rudolph

Reputation: 545668

This doesn’t work.

Assume you have a different class, OtherEntity, that would also implement the interface. If your above code would work, the method Calculate could add an instance of OtherEntity to your list of Entity:

Dim entities As New List(Of Entity)()
Dim weightables As List(Of IWeightable) = entities ' VB forbids this assignment!
weightables.Add(New OtherEntity())

That is illegal. If it weren’t, what would the content of entities(0) be?

To make the code work, use a generic method with a constraint instead:

Public Sub Calculate(Of T As IWeightable)(ByVal entites As IList(Of T))
    ...
End Sub

Upvotes: 6

Related Questions