Search code examples
.netvb.netgenericscastingtype-parameter

Elements of ConcurrentQueue(Of T) cannot be widened


Let's say I have this Sub:

Sub Process(InputQueue as ConcurrentQueue(Of Object))
    .... some codes ....
End Sub

Now I have this

Class JobDesc
    .... some codes ....
End Class

Dim MainJobQueue As New ConcurrentQueue(Of JobDesc)

Why does VB.Net complain if I try to pass MainJobQueue as a parameter to Process()?

That is, doing this:

Process(MainJobQueue)

Will result in the following error message:

Value of type 'ConcurrentQueue(Of JobDesc)' cannot be converted to 'ConcurrentQueue(Of Object)'.

Is not JobDesc a subclass of Object, hence casting JobDesc to Object will be a 'widening' instead of narrowing?


Solution

  • Is not JobDesc a subclass of Object, hence casting JobDesc to Object will be a 'widening' instead of narrowing?

    Yes, but the problem is that ConcurrentQueue is a mutable collection.

    So its type parameter does not only define what objects you can get out of it, but also what new elements it will accept.

    If you could straight-up cast it to ConcurrentQueue(of Object), you could then do this:

    Sub Process(InputQueue as ConcurrentQueue(Of Object))
        InputQueue.Enqueue("Whoops, this is not a JobDesc!")
    End Sub
    
    Process(MainQueue)
    
    Dim getLatestJob = MainQueue.Dequeue() // WTF, why did it give me a String?
    

    (Well, in theory. If you actually managed to compile this, the program would crash with InvalidCastException.)

    Now, if you don't plan to add any non-JobDesc elements to the queue, you have a couple of options.

    • You can enforce the type constraint by writing Process as a generic method instead.

      Sub Process(Of SomeType)(InputQueue as ConcurrentQueue(Of SomeType))
           InputQueue.Enqueue("Whoops, this is not a SomeType!")
           // will not compile, input is not SomeType
      End Sub
      
    • Or, you can cast ConcurrentQueue to a base class or interface like IEnumerable(Of T), which does not allow adding new elements to the collection, and therefore can be safely cast to a a broader type like Object (technical term: covariant).

       Dim MainQueueReadOnly As IEnumerable(Of JobDesc) = MainQueue
      
       Sub Process(InputQueue as IEnumerable(Of Object))
           InputQueue.Enqueue(anything) 
           // will not compile as IEnumerable doesn't have an .Enqueue method
       End Sub
      

    If you do, however, intend to stick some Objects in the queue, then your only option is to create a new, different collection from InputQueue, one that will accept elements of types other than JobDesc.

    Dim newQueue As New ConcurrentQueue(Of Object)(MainJobQueue)