Stécy
Stécy

Reputation: 12367

C#: Declaring and using a list of generic classes with different types, how?

Having the following generic class that would contain either string, int, float, long as the type:

public class MyData<T>
{
    private T _data;

    public MyData (T value)
    {
        _data = value;
    }

    public T Data { get { return _data; } }
}

I am trying to get a list of MyData<T> where each item would be of different T.

I want to be able to access an item from the list and get its value as in the following code:

MyData<> myData = _myList[0];    // Could be <string>, <int>, ...
SomeMethod (myData.Data);

where SomeMethod() is declared as follows:

public void SomeMethod (string value);
public void SomeMethod (int value);
public void SomeMethod (float value);

UPDATE:

SomeMethod() is from another tier class I do not have control of and SomeMethod(object) does not exist.


However, I can't seem to find a way to make the compiler happy.

Any suggestions?

Thank you.

Upvotes: 29

Views: 45179

Answers (9)

Leon van der Walt
Leon van der Walt

Reputation: 1017

Delegates can really help simplify this, and still keep things type-safe:

public void TestMethod1()
{
    Action<SomeClass, int> intInvoke = (o, data) => o.SomeMethod(data);
    Action<SomeClass, string> stringInvoke = (o, data) => o.SomeMethod(data);

    var list = new List<MyData> 
    {
        new MyData<int> { Data = 10, OnTypedInvoke = intInvoke },
        new MyData<string> { Data = "abc", OnTypedInvoke = stringInvoke }
    };

    var someClass = new SomeClass();
    foreach (var item in list)
    {
        item.OnInvoke(someClass);
    }
}

public abstract class MyData
{
    public Action<SomeClass> OnInvoke;
}

public class MyData<T> : MyData
{
    public T Data { get; set; }
    public Action<SomeClass, T> OnTypedInvoke 
    { set { OnInvoke = (o) => { value(o, Data); }; } }
}

public class SomeClass
{
    public void SomeMethod(string data)
    {
        Console.WriteLine("string: {0}", data);
    }

    public void SomeMethod(int data)
    {
        Console.WriteLine("int: {0}", data);
    }
}

Upvotes: 10

Franci Penov
Franci Penov

Reputation: 76021

You can't do it the way you want.

When an instance of a generic class is initialized, it is bound to particular type. Since you want to hold objects of different types in your list, you have to create an instance bound to the least common denominator — in your case it's Object.

However, that means that Data property now will return an object of type Object. The compiler cannot infer the actual data type at compile time, so it can choose the appropriate SomeMethod overload.

You have to either provide an overload of SomeMethod that takes Object as a parameter, or remove the requirement to hold different such different types in your collection.

Or you can go with a standard IEnumerable collection (like Array) and use the OfType<> extension method to get the subset of the collection of particular type.

Upvotes: 4

Kent Boogaart
Kent Boogaart

Reputation: 178810

Suggested wildcards a while back here. Closed as "won't fix" :(

Upvotes: 1

Hosam Aly
Hosam Aly

Reputation: 42474

You can create a generic wrapper for SomeMethod and check for the type of the generic argument, then delegate to the appropriate method.

public void SomeMethod<T>(T value)
{
    Type type = typeof(T);

    if (type == typeof(int))
    {
        SomeMethod((int) (object) value); // sadly we must box it...
    }
    else if (type == typeof(float))
    {
        SomeMethod((float) (object) value);
    }
    else if (type == typeof(string))
    {
        SomeMethod((string) (object) value);
    }
    else
    {
        throw new NotSupportedException(
            "SomeMethod is not supported for objects of type " + type);
    }
}

Upvotes: 1

Joseph
Joseph

Reputation: 25533

I think the issue that you're having is because you're trying to create a generic type, and then create a list of that generic type. You could accomplish what you're trying to do by contracting out the data types you're trying to support, say as an IData element, and then create your MyData generic with a constraint of IData. The downside to this would be that you would have to create your own data types to represent all the primitive data types you're using (string, int, float, long). It might look something like this:

public class MyData<T, C>
    where T : IData<C>
{
    public T Data { get; private set; }

    public MyData (T value)
    {
         Data = value;
    }
}

public interface IData<T>
{
    T Data { get; set; }
    void SomeMethod();
}

//you'll need one of these for each data type you wish to support
public class MyString: IData<string>
{
   public MyString(String value)
   {
       Data = value;
   }

   public void SomeMethod()
   {
       //code here that uses _data...
       Console.WriteLine(Data);
   }

   public string Data { get; set; }
}

and then you're implementation would be something like:

var myData = new MyData<MyString, string>(new MyString("new string"));    
// Could be MyString, MyInt, ...
myData.Data.SomeMethod();

it's a little more work but you get the functionality you were going for.

UPDATE: remove SomeMethod from your interface and just do this

SomeMethod(myData.Data.Data);

Upvotes: 14

Mehrdad Afshari
Mehrdad Afshari

Reputation: 422280

Inherit MyData<T> from a non-generic MyData class and make a list of that.

This way, you can't automatically resolve the overload. You have to do it manually.

abstract class MyData { 
   protected abstract object GetData();
   protected abstract Type GetDataType(); 
   public object Data {
      get { return GetData(); } 
   }
   public Type DataType {
      get { return GetDataType(); } 
   }
}

class MyData<T> : MyData { 
   protected override object GetData() { return Data; }
   protected override Type GetDataType() { return typeof(T); }
   public new T Data {
     get { ... }
   }
}

Upvotes: 0

Amy B
Amy B

Reputation: 110221

Just use an ArrayList and forget the MyData<T> type.

ArrayList myStuff = getStuff();
float x = myStuff.OfType<float>().First();
SomeMethod(x);
string s = myStuff.OfType<string>().First();
SomeMethod(s);

The problem with MyData<T> is that you're expecting the compiler to check a type that is only known at runtime. Compilers check types that are known at compile time.

Upvotes: 6

Mendelt
Mendelt

Reputation: 37503

Generics allow you to specify one type for the whole list when you create the list, for example a list for storing int would be created like this

var myData = new MyData<int>();

If you want to store multiple types in the same generic list you can specify a common base type or interface for those types. Unfortunately in your case the only common base type for the types you want to store would be object.

var myData = new MyData<object>();

But you can just use the non-generic list for storing objects.

Upvotes: 0

AnthonyWJones
AnthonyWJones

Reputation: 189555

In that case you need MyData<object> since that is the only thing those types have in common.

Upvotes: 1

Related Questions