tags:

views:

196

answers:

6

Need to have a type-safe bag of items that all implement a generic interface.

The desire is to do something like:

var stringItem = new IItem<string>();
var numberItem = new IItem<int>();
var items = new List<IItem<T>>(); //T of course doesn't accomplish what I want

items.Add(stringItem);
items.Add(numberItem);

Something like:

interface IItem
{
   object Value { get; set; }
}

//Update: 2009-03-19 03:08 PM MST
//Added the following interface for clarity of my question

interface IItem<T> : IItem
{
   new T Value { get; set; }
}

Then, I could:

var items = new List<IItem>();

But, I lose type safety in my bag. So, I thought of Dictionary:

var dict = new Dictionary<Type, List<IItem<T>>>(); //T is wrong again

dict.Add(typeof(string), new List<IItem<string>>); //that sure looks nice
A: 

I was thinking about exactly this same problem a few weeks ago. Think about what you're trying to do: store different types in the same collection. Even generics aren't going to help you out here.

The only solution (that I know of) is to use List<Object> and cast when you pull the items out.

Paul Suart
Yes, I am aware of object. Object is not an answer for intrinsic type-safety in my question.
Eric Swanson
Unfortunately, as the other answerers have pointed out, I don't think you're ever going to get it. The polydictionary looks to be the closest to what you want.
Paul Suart
+1  A: 

Check out the PolyDictionary implementation here.

class Key<T> { public Key() { } }

class PolyDictionary {
    private Dictionary<object, object> _table;

    public PolyDictionary() {
        _table = new Dictionary<object, object>();
    }

    public void Add<T>(Key<T> key, T value) {
        _table.Add(key, value);
    }

    public bool Contains<T>(Key<T> key) {
        return _table.ContainsKey(key);
    }

    public void Remove<T>(Key<T> key) {
        _table.Remove(key);
    }

    public bool TryGetValue<T>(Key<T> key, out T value) {
        object objValue;
        if (_table.TryGetValue(key, out objValue)) {
            value = (T)objValue;
            return true;
        }
        value = default(T);
        return false;
    }

    public T Get<T>(Key<T> key) {
        T value;
        if (!TryGetValue(key, out value))
            throw new KeyNotFoundException();
        return value;
    }

    public void Set<T>(Key<T> key, T value) {
        _table[key] = value;
    }
}
Dustin Campbell
not intrinsically type-safe - which is the goal.
Eric Swanson
You cannot avoid casting in any answer to your problem, it has to be somewhere. You could have PolyDictionary cast NodeBase to Node<T> instead of object to T, but does this improve matters any?
Anton Tykhyy
Besides, this PolyDictionary *is* type-safe. It stores data as object internally, so what? Ultimately, data is stored in non-typed memory cells as a chunk of bytes :)
Anton Tykhyy
@ericis please educate me on how this is not type-safe. This is perfectly type-safe to use. The internal storage mechanism, may cast to object, but that is encapsulated and hidden from the user. From the user's, perspective it is type-safe.
Dustin Campbell
@Dustin - You are correct. This is type-safe to the public and I suppose some good unit test coverage would make this even more reliable. I say "intrinsically", because internally it could be corrupted and you're always having to "guard" against type evils in this code.
Eric Swanson
This is not "typesafe" because it allows me to call Add<int> against a dictionary that I intend to only contain IItems.
David B
@erics This is completely self contained. It is completely type-safe and the compiler can reason about it as such.
Dustin Campbell
@David-b that actually doesn't make sense. :-) Please read the article to see how this is used.
Dustin Campbell
+1  A: 

You should be able to get away with doing something like the following

interface IItem { object Value {get; set;}}

interface IItem<T> : IItem { T Value {get; set;}}

var items = new List<IItem>();

items.add(new IItem<string>());
items.add(new IItem<int>());

But you'll still have to do some casting when you pull it out.

Gregg
new IItem<>?
Marc Gravell
I wrote the base-interface in my question. :) Oh, and I believe IItem<T> would need to inherit from IItem and override the Value...
Eric Swanson
Fixed the code sample, the IItem<T> was supposed to inherit from IItem
Gregg
+5  A: 

I don't think you can escape the fact that IItem<int> and IItem<string> are different; the usual approach is a base-interface:

interface IItem {
   object Value {get;}
}
interface IItem<T> : IItem {
   new T Value {get;}
}

That way, you code against IItem, but the actual instances (that typically implement IItem<T> for some T) are stll strongly-typed internally.

Marc Gravell
In Protocol Buffers I do something similar, but avoid reusing names - I have "WeakFoo()" in the weak type, and "Foo()" in the strong type. This particularly helps when WeakFoo() returns an IItem and Foo() returns an IItem<T>.
Jon Skeet
I wrote the base-interface in my question. :)
Eric Swanson
@ericis - IMO, the key thing is the fact that IItem<T> : IItem; if you already had that, then fine - but it wasn't stated AFAIK
Marc Gravell
No worries. Thanks Marc!
Eric Swanson
A: 

Make a new class which has the sole purpose of boxing the values you are interested in. - This class should accept either a StringItem, OR a NumberItem. - The class should allow you access to the string or number item member.

WHen you a dd your objects to the list, you will need to box your objects, and then unbox them when you take them out again.

If this was C++, then the overhead of this design should not be more than an additional pointer dereference, depending on how you implement it, however the key phrases here are "boxing" and "unboxing".

Arafangion
A: 
public class GrabBag<T>
{
    private ArrayList inner = new ArrayList();

    public void Add(T item)
    {
        inner.Add(item);
    }

    public IEnumerable<U> Get<U>() where U : T
    {
        return inner.OfType<U>();
    }
}

Or, you could just use the generic List and call OfType. That makes more sense.

David B