0

I'm having difficulty, probably missing something simple, with queuing and unqueuing items in a collection.

I have classes with various levels of inheritance:

public abstract class Item
{  //stuff }

public class DetailItem : Item
{  //stuff }

public class SuperDetailItem : DetailItem
{  //stuff }

public class OddItem : Item
{  //stuff }

I then have a ConcurrentQueue<T> like so:

public class CQItems<Item> : ConcurrentQueue<Item>
{  //some methods }

One of the methods adds items when called, so I do something like:

CQItems<Item> items = new CQItems<Item>();
items.Generate(i);

It'll create i items in the queue. This works, though currently in the method I am specifying creating DetailItem items - I'd like to have that be dynamic if possible as well, but that'll be a different question. So later, I call to get a DetailItem out like so:

 DetailItem item;

 items.TryDequeue(out item);

and get the message "Cannot convert from DetailItem to Item".

I don't want to make a specific collection for each inherited type. What piece am I missing? Thanks!

EDIT: Update for additional information. Making Item an Interface and fixing it so that common methods were defined in the first children of the interface didn't resolve the issue, even making the collection a collection of type IItem.

I'm adding some details here in case I'm approaching this the wrong way entirely. The software is a QA tool for other software that deals with banking. Items can be Cash : Item (or IItem), Check: Base, ReturnCheck : Check and a handful of others. Each descendant has some additional information that the class object needs to carry around that the others do not care about. Some also require methods that their parent classes do not need. This is why I don't want to use just a single class with all of the elements and an Enum for the type - different items will need to be validated differently and used differently. For instance, when Item was virtual, rather than an interface, it had a field Amount. It's the only field that ALL children and grandchildren will always have.

There will be some cases where I'd like to have the collection class contain some CheckItem objects and some CashItem objects. In those cases, the code that pulls items from the queue would evaluate the type of the item and work on it accordingly, as well as provide the appropriate information to the calling function.

Currently, with either the virtual or interface implementation, it KNOWS that it's say... a CheckItem. But since the base class doesn't have a field for "CheckNo", when I just do item.CheckNo, it says the Item base class doesn't have that field (which no, it doesn't).

Currently I have it working by doing this:

IItem _item;
CheckItem item;

items.TryDequeue(out _item);
item = _item as CheckItem;

Which is great if there's only ever one type of object in the collection at a time. Is this something that I cannot do, or am I approaching it incorrectly?

Jesse Williams
  • 653
  • 7
  • 21
  • You can make your collection covariant or contravariant but not invariant. You say *wtf*? I know, you will need to study those terms and you will find out why you cannot do what you want to do. – CodingYoshi Nov 03 '18 at 15:15
  • Covariant is precisely my goal, which is why I used ConcurrentQueue, which is the base class. – Jesse Williams Nov 03 '18 at 15:20
  • You cannot. Not in C#. When you put more than one type into a generic collection, it is not a collection of 1 type anymore. So it is like an arraylist. – CodingYoshi Nov 03 '18 at 15:24
  • Hmmmm - interesting. – Jesse Williams Nov 03 '18 at 15:26
  • 1
    A better solution would be to ask yourself "what common operations do you want to perform on these items in the collection" and get all your items to implement a common interface. Make the collection of that interface type. Now your generic collection, is totally generic. If you have to cast things to a specific type, it is a sign for design flaw. – CodingYoshi Nov 03 '18 at 15:34
  • Revoking this comment - I read that regarding the object classes rather than the collection class. I'll evaluate that. – Jesse Williams Nov 03 '18 at 16:23

1 Answers1

1

Firstly you create a queue which is supposed to store Item elements.

CQItems<Item> items = new CQItems<Item>();

Then you say: take whatever concrete type deriving from Item you have and assign it to DetailItem variable:

DetailItem item;
items.TryDequeue(out item);

TryDequeue "returns" an Item via out modifier. This is basically the same thing as if you tried to assign an Item to a DetailedItem like so:

Item item = GetItem();
DetailedItem detailedItem = item; // compilation error

Which obviously makes no sense.

The "Try" part in TryDequeue corresponds to the empty queue, not to the casting.

What you probably want to do is:

Item item;
items.TryDequeue(out item);
if (item is DetailItem)
{
    DetailItem detailedItem = (DetailItem) item;
    detailedItem.WhateverDetailedItemCanDo();
}

Or if your are using C# 7:

items.TryDequeue(out Item item);

if (item is DetailItem detailedItem)
{
    detailedItem.WhateverDetailedItemCanDo();
}

And BTW, it has nothing to do with a "custom collection". If you used ConcurrentQueue instead of CQItems, the result would be the same.

Andrzej Gis
  • 13,706
  • 14
  • 86
  • 130
  • I think maybe I’m having difficulty understanding the way that covariance and contravariance are applied. I was assuming I could cast the out as the type of derived class I wanted it to be (or rather that it was created as). I didn’t understand, however, that dequeuing it as an Item doesn’t prevent it from still being a DetailItem. Since that’s the case, I think that’s all I need to do. My primary goal was to allow a collection to contain multiple derived types at once, if possible, and I think this would allow me to do that. – Jesse Williams Nov 03 '18 at 15:26
  • 1
    @JesseWilliams Do you expect me to add anything to this answer? If it's just the covariance and contravariance, take look at my answer to the other question. It's quite lengthy, but should help you "feel" it: https://stackoverflow.com/a/53134957/672018 – Andrzej Gis Nov 03 '18 at 19:59