tags:

views:

122

answers:

8

If I have a generic class:

public class MyClass<T> 
{
  public T Value;
}

I want to instantiate several items such as...

new MyClass<string>
new MyClass<int>

...and add them to a collection. How do I define the collection so that it can hold a list of generic types? I then want to iterate through the collection at some point, and use the Value property. Possible?

A: 

I believe your collection would all have to be of the same MyClass type (as in T would have to be the same), because the compiler won't know what types you'd added to which elements in the collection.

In other words, if you were to add 2 elements to a list:

list.Add(new MyClass<string>());
list.Add(new MyClass<int>());

then try to reference one:

var myItem = list[1];

The compiler doesn't know what generic was assigned to the MyClass list at element 1, because the elements are added at runtime, but the generics are determined at compile time.

I'm pretty sure that what you want to do can't be done.


If you know the number of elements in advance, perhaps you could use a Tuple?

rally25rs
+5  A: 

The only way I can think of, off the top of my head is as follows (wrapped up in a Console app for testing):

class Program
{
    static void Main(string[] args)
    {
        var x = new MyClass<string>() { Value = "34" };
        var y = new MyClass<int>() { Value = 3 };

        var list = new List<IMyClass>();
        list.Add(x);
        list.Add(y);

        foreach (var item in list)
        {
            Console.WriteLine(item.GetValue);
        }
    }

    private interface IMyClass
    {
        object GetValue { get; }
    }

    private class MyClass<T> : IMyClass
    {
        public T Value;

        public object GetValue
        {
            get
            {
               return Value;
            }
        }
    }
}

i.e. Have MyClass implement an empty interface and then create your collections as one that holds instances of classes that implement that interface.

Update: I've added a "GetValue" method to the interface that allows you to access the "Value" of the MyClass instance as an Object. This is about as good as it's going to get, afaik, if you want to have a collection that holds mixed types.

Rob
good call - this is an equivalent answer to mine using a base class :)
Josh E
But how do you access the value property?
Tim Coker
The issue is that he wants a Value property that ends up having the correct type already, which can not be determined.
rally25rs
How do I iterate through the list and check the contents of the Value property?
Jeremy
you could make the interface an abstract class, with the properties already defined as `object` and the generic version overrides typewise?.
FallingBullets
In which case you could have just started with a List<object> and called it a day... (scratch that, I re-read it and realized that the interface at least provides you with the Value property).
rally25rs
@Jeremy - I've updated my answer to include a "GetValue" method in the interface that returns the "Value" property of MyClass as an object. As far as I know, that's as good as it gets, at least in 3.5 and below. There may be some funky stuff you can do in 4.0 with Co/Contra-Variance, but I've yet to explore that :)
Rob
But, as rally25s suggests, since the only access to the stored data is through an Object, then you might as well just store it as an Object, and make this a non-generic class.
James Curran
that's not entirely true James -- using reflection, you can easily get the type, and from there the value of the generic class's property. See comments in my answer
Josh E
+3  A: 

You'll want to define a base class for MyClass, then your collections will be a List of base class. Ex:

void Main()
{
 var a = new MyClass<string>();
 var b = new MyClass<int>();
 var c = new List<MyBase>();
 c.Add(a);
 c.Add(b);

}

public class MyBase { }
// Define other methods and classes here
public class MyClass<T> : MyBase {
public T Value { get; set;}
}
Josh E
It might be worth, in your example, marking MyBase as an abstract class to make it clearer what the intent is,.. :)
Rob
Interesting... How do I iterate through the list and check the contents of the Value property?
Jeremy
@Josh E - +1 from me, pretty much the same idea as my answer :)
Rob
you'll want to iterate through the collection, and inside of that, call GetType().GetGenericParameters().First() to get the type, then extract Value as an object and cast it to the previous type extracted... it's a lot of work, so you may want to consider the purpose of this logic and see if there isn't a different way to accomplish the same result
Josh E
+2  A: 

You want to have a collection of MyClass for which the value of the type parameter T is different in each instance. This is not possible in .NET; it lacks the equivalent of the Java wildcard (?). What you need to do instead is create a non-generic base class or interface, which MyClass can implement. For example:

public interface IMyClass {
  object Value { get; set; }
}
public class MyClass<T> : IMyClass {
  public T Value { get; set; }
  object IMyClass.Value {
    get { return Value; }
    set { Value = (T)value; }
  }
}
List<IMyClass> m = new List<IMyClass> { new MyClass<string>(), new MyClass<int> };
@commongenius, did you verify it won't compile? It compiled for me just fine in VS2k8
Rob
By the time I had posted my comment, the original post had been changed. I changed my comment accordingly.
+1  A: 

I have interfaces on most of my generic types with "Untyped" members:

private interface IMyClass
{
    object UntypedValue { get; }
}

private class MyClass<T> : IMyClass
{
    T Value { get; set; }

    object UntypedValue { get { return Value; } }
}

You could also do this by the use of explicit interface implementation, but in my opinion, it is much easier by using a separate name. (There are some CA hints on explicit interface implementation as well)

Stefan Steinegger
+1 for offering both typed and untyped values.
Steven Sudit
A: 

IList

There is no way to define a generic collection that can accept any flavor of your generic class... ie IList<MyClass>. Generic classes are only a short cut for the developer to save on writing a bunch of repetitive code but at compile time each flavor of the generic class is translated into a concrete. i.e. if you have MyClass<string>, MyClass<int>, MyClass<bool> then the compiler will generate 3 seperate and distinct classes. The only way to do what you want is to have an interface for your generic.

public interface IMyGeneric {
    Type MyType { get; set;}    
}

class MyGeneric<T> : IMyGeneric {

    public MyGeneric() {
        MyType = typeof(T);
    }

    public Type MyType {
        get; set;
    }
}

and then you can say

IList<IMyGeneric> list = new List<IMyGeneric>();
list.add(new MyGeneric<string>());
list.add(new MyGeneric<int>());
Mogounus
A: 

I think the problem here is that generic classes really aren't the same type at all. They're simply templates which create entire new types at compile time (if I understand correctly). Therefore, MyClass<int> and MyClass<string> are completely different types, according to the runtime. They might as well be MyIntClass and MyStringClass, which you obviously cannot have in the same list without boxing them first. They don't (necessarily) inherit the same base class, implement the same interfaces, or anything else. They're as different as any other two types out there, and you have to treat them as such (even though you think you know better).

Of course, you can have them implement an interface, inherit a base object, or any of the other options already given. Take a look at commongenius' answer for a good way to do this.

Daniel Rasmussen
+2  A: 

Have your generic class inherit from a non-generic base, or implement a non-generic interface. Then you can have a collection of this type and cast within whatever code you use to access the collection's contents.

Here's an example.

public abstract class MyClass
{
    public abstract Type Type { get; }
}

public class MyClass<T> : MyClass
{
    public override Type Type
    {
        get { return typeof(T); }
    }

    public T Value { get; set; }
}

// VERY basic illustration of how you might construct a collection
// of MyClass<T> objects.
public class MyClassCollection
{
    private Dictionary<Type, MyClass> _dictionary;

    public MyClassCollection()
    {
        _dictionary = new Dictionary<Type, MyClass>();
    }

    public void Put<T>(MyClass<T> item)
    {
        _dictionary[typeof(T)] = item;
    }

    public MyClass<T> Get<T>()
    {
        return _dictionary[typeof(T)] as MyClass<T>;
    }
}
Dan Tao
Returning the type is an interesting variation.
Steven Sudit
your solution is definitely elegant. Keeping an internal dictionary of types does make for simpler access methods.
Josh E