views:

3301

answers:

9

Hi,

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.

A: 

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 { ... }
   }
}
Mehrdad Afshari
Ok, now I would use it like:List<MyData> list = new List<MyData>();list.Add (new MyData<int>());list.Add (new MyData<string>());SomeMethod (list[0].Data);Somehow, I will need to have SomeMethod() accept an object.This is not what I need unfortunately.Thanks for replying.Stecy
Stecy
Yes, that's why I said you should manually manage overloads by considering the type. There is not a direct way supported by generics.
Mehrdad Afshari
+3  A: 

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

AnthonyWJones
How does MyData<object> allow the calling of SomeMethod(int) ?
David B
A: 

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.

Mendelt
+2  A: 

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.

David B
+2  A: 

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 iwll returnd an object of type Object. The compiler cannot infere the actual data type at compile time, so it can choose the apppropriate 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.

Ot 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.

Franci Penov
I was afraid it would resolve to using an object...However, I can't use an object since SomeMethod is from a library and I can't change the fact that it won't accept an object...Also, I don't want to switch() on the type of the variable...
Stecy
A: 

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

HTH, Kent

Kent Boogaart
+5  A: 

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);
Joseph
Too bad I need to wrap the primitive types but that is something I am willing to accept as there is no other way it would seem.However, in your example, you assumed that SomeMethod() was part of MyData which is not the case (I've updated the question).
Stecy
actually you can accomplish the same thing, i just wrapped it so it would be more encapsulated, see my update
Joseph
So... how does one place MyData<MyString, string> and MyData<MyFloat, float> into the same collection?
David B
you would have to create another interface say IDataType and redefine the IData<T> interface to say IData<T> where T : IDataType, and then every concrete type would have to implement IDataType as well, say class MyString: IData<string>, IDataType
Joseph
then you could say instantiate a list like so: new List<IDataType>(); and pass in both MyData<MyString, string> as well as MyData<MyFloat, float> as long as both are of type IDataType.
Joseph
+1  A: 

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);
    }
}
Hosam Aly
Thanks for your answer.I'm afraid I will have to use boxing/unboxing after all.I thought that there must be a more efficient way..
Stecy
A: 

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);
        }
    }
Leon van der Walt