In one project (assembly), I have some interfaces:
public interface ISecurable
{
int SecurableID { get; set; }
string SecurableString { get; }
ISecurableContainer SecurableParent { get; }
}
public interface ISecurableContainer : ISecurable
{
List<ISecurable> SecurableChildren { get; }
}
In another project, I have some classes:
public class Item : IComaprable<Item>
{
...
}
public class ItemCollection : List<Item>
{
...
}
public class Detail
{
private ItemCollection items = new ItemCollection();
...
public ItemCollection Items { get { return items; } }
}
In my intranet project, I wrap the Item and Detail classes as securabls:
public class SecurableItem : Item, ISecurable
{
...
}
public class SecurableDetail : Detail, ISecurableContainer
{
private List<SecurableItem> secItems = new List<SecurableItem>();
...
// need a new Items property to use List<SecurableItem>
new public List<SecurableItem> Items { get { return secItems; } }
// implement ISecurableContainer property: SecurableChildren
// THIS IS WHERE THE PROBLEM IS
public List<ISecurable> SecurableChildren
{
get
{
return secItems; // <-- compiler error
return (List<ISecurable>)secItems; // <-- throws exception
}
}
}
The compiler error is the "Cannot implicitly convert..." and the exception thrown is the "Invalid Cast...".
The problem is, even though an instance of SecurableItem is polymorphic with Item and ISecurable, the collections are not. If I have a method that takes, say, an object of List<ISecurable>:
public void SomeMethod(List<ISecurable> list)
{
...
}
I cannot pass it an object of List<SecurableItem>:
...
private SecurableDetail detail = new SecurableDetail();
...
SomeMethod(detail.Items);
will not compile (Cannot implicitly convert...)
SomeMethod((List<ISecurable>)detail.Items);
will not run (exception: Invalid Cast...).
Furthermore, I cannot call Sort() on List<SecurableItem>. I can a runtime error "At least one object must implement IComparable". The Item class implements IComparable and SecurableItem extends (inherits) Item. Why can't I call Sort() on List<SecurableItem>
Any insight would be greatly appreciated.
/js

Polymorphic Collections
Coldwine
I would just change the list to what you (really) need:
private List<SecurableItem> secItems = new List<SecurableItem>();
to
private List<ISecurable> secItems = new List<ISecurable>();
Obviously you can insert the objects of SecurableItem into this kind of list as they implement ISecurable and you should be able to return it as is.
JP3
Thanks for the reply, John.Doe.
So, you are suggesting that I maintain 3 lists of the same objects Something like:
public class SecurableDetail : Detail, ISecurableContainer
{
private List<ISecurable> iSecItems = new List<ISecurable>();
private List<SecurableItem> secItems = new List<SecurableItem>();
...
public SecurableDetail()
{
foreach(Item item in this.Items)
{
iSecItems.Add(item);
secItems.Add(item);
}
}
...
public List<SecurableItem> SecurableItems { get { return secItems; } }
// implement ISecurableContainer property: SecurableChildren
public List<ISecurable> SecurableChildren { get { return iSecItems; } }
}
Which would certainly work as long as none of the collections changes. Adding or removing items to any of the 3 collections would require adding or removing the same item in the other collections. Which, isn't possible as it stands. I would have to create custom collection classes, implement a CollectionChanged event that the SecurableDetail class would subscribe to so it can maintain the 2 collections that didn't change.
This seems to be a lot of work and overhead for something that should already work. However, I might just try this and see how it works out.
I did find that what I'm trying to do is use variance, which C# doesn't support in generics. MSDN has a nice writeup about it, why it's not supported, and some example workarounds:
(msdn local)ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.en/dv_csref/html/0bde6ce3-4012-4843-bdc6-9bcbc43271dc.htm
(msdn onlin)http://msdn2.microsoft.com/en-us/library/ms228359.aspx
Thanks to Jesse Liberty for pointing me to the variance issue.
/js
BrianLag
Inbal
liquidPhix
raptorpete
I say it's not an option because of encapsulation and I desire that encapsulation.
The first assembly is a class library for a ticket system (the Detail and Item classes are part of this assembly). The classes in that assembly aren't concerned with how or where they're going to be used. They shouldn't need to. The methods in these classes will never take, for example, List<ISecurable> as a parameter. All methods take references of the classes in this assembly (ie. List<Item> or List<Detail> or List<Ticket>).
The second assembly is a class library for a pluggable object-based security system. It defines some interfaces and is coded against those interfaces. It doesn't know or care about what objects are securable, only that they implement ISecurable.
This is, as far as what I've learned, good and proper OOD.
The third assembly is the intranet project. Everything in the intranet is securable. The web pages, the menu items, the ticket system, the time clock, the scheduler, the "work areas" (forum, file sharing, calandar/scheduler, project management, etc). Anything that is to be a securable implements ISecurable and can have permissions set on it. Since the classes that are to implement ISecurable are abstracted out, I figured I needed to make wrapper classes to wrap ISecurable on the classes I wanted to secure. The resulting class can be used in the tickets assembly because it extends those classes and they can be use in the sercurity assembly because they implement ISecurable.
This is exactly the kind of behavior I'm looking for.
However, when dealing with collections, I ran into the covariance problem.
Now, since I'm the coder of all these assemblies, I know that the methods in the tickets and security assemblies that take collection parameters are only iterating through them - not manipulating them. They really only need read-only access to the collections. So, in this case, the reason covariance wasn't implelmented isn't applicable.
So now, I'm thinking that I can make the properties build the proper collection "on-the-fly" and not worry about synchonization becuase there won't be any need.
public class SecurableDetail : Detail, ISecurableContainer
{
private List<SecurableItem> secItems = new List<SecurableItem>();
...
// expose List<SecurableItem>
public List<SecurableItem> SecurableItems { get { return secItems; } }
// re-define the Detail property: Items
new public List<Item> Items
{
get
{
List<Item> newList = new List<Item>();
foreach(SecurableItem item in secItems)
newList.Add(item);
return newList;
}
}
// implement ISecurableContainer property: SecurableChildren
public List<ISecurable> SecurableChildren
{
get
{
List<ISecurable> newList = new List<ISecurable>();
foreach(SecurableItem item in secItems)
newList.Add(item);
return newList;
}
}
}
It just doesn't "feel" right. It also feels sloppy, like it's not quite the right way to do things.
MSDN (the link I posted earlier) gives some example code to workaround the lack of covariance support. In particular, the variance in one direction by providing an EnumeratorWrapper is appealing. I won't have to re-create the lists, but I would have to re-code all the methods to take IEnumerable<>. While this way is a lot more work for me, it "feels" better than the other way.
public class SecurableDetail : Detail, ISecurableContainer
{
private List<SecurableItem> secItems = new List<SecurableItem>();
...
public List<SecurableItem> SecurableItems { get { return secItems; } }
// re-define the Detail property: Items
new public IEnumerable<Item> Items { get { return VarianceWorkaround.Convert<SecurableItem, Item>(secItems); } }
// implement ISecurableContainer property: SecurableChildren
public IEnumerable<ISecurable> SecurableChildren { get { return VarianceWorkaround.Convert<SecurableItem, ISecurable>(secItems); } }
}
Thoughts
/js
codeman43
Yes, SecurableItem implements ISecurable, but that doesn't translate to List<SecurableItem> implements List<ISecurable>.
public class SecurableDetail : Detail, ISecurableContainer
{
private List<ISecurable> items = new List<ISecurable>();
...
// implement ISecurableContainer property: SecurableChildren
public List<ISecurable> SecurableChildren { get { return items; } }
}
Sure, I can add SecurableItem objects to the collection, but that isn't the problem.
How do I provide that same list to methods that are expecting List<Item> or List<SecurableItem>
/js
Botia
I'm sorry, I don't understand what you're getting at.
Are you saying that instead of having methods that take a List<Item>, methods that take a List<ISecurable>, and methods that take a List<SecurableItem> and make them all take the same list - in this case List<ISecurable>
If that is what you are saying, that's not an option - that cannot happen.
If that is not what you are saying, could you please elaborate
/js