tags:

views:

1195

answers:

7

Hello, and thanks for any assistance.

How would I return from a method an unknown Generic.List type.

public void Main()
{
  List<A> a= GetData("A");   
}

public List<T> GetData(string listType)
{
   if(listType == "A")
   {
     List<A> a= new List<A>() 
     ...
     return a; 
   }
   else
   {
     List<B> b = new List<B>()
     return b;

   }
}

In the below example I recieve an error similar to: Can't Convert List<A> to List<T>

Is this possible? The error occurs on the 'return a;' line of code.
Also, What will I need to do to make sure an error does not occur on the line:

List<A> a= GetData("A");

Thanks, Steven

+4  A: 

Use IList instead of List<T>.

John Rasch
Agreed that should be the eventual goal, but it doesn't really help him solve the immediate problem.
Joel Coehoorn
+2  A: 

EDIT per Orion's answer below, added contraint that AnthonyWJones suggested

you probably should have an interface/abstract class that A and B are inheriting from

    public interface IMyInterface { }
    public class A : IMyInterface { }
    public class B : IMyInterface { }

    public List<IMyInterface> GetData<T>() where T : IMyInterface
    {
        List<IMyInterface> myList = new List<IMyInterface>();
        if (typeof(T) == typeof(A))
        {
            myList.Add(new A());
        }
        if (typeof(T) == typeof(B))
        {
            myList.Add(new B());
        }
        return myList;
    }
Jon Erickson
You forgot the type parameter- added it in for you.
Joel Coehoorn
if( typeof(T) == typeof(A) ) makes sad panda :-(
Orion Edwards
@Orion How could I make that better? i agree it looks ugly, but my idea was accomplished.
Jon Erickson
+6  A: 

You can't directly return a List<T> like this.

Why? Basically because List<A> and List<B> (or List<string> vs List<int> which is the same thing) are considered as 2 totally seperate unrelated classes.
Just as you can't return a string from a function which is declared to return int, you can't return a List of strings from a function which is declared to return a list of ints. The <T> here is a bit of a red herring. You couldn't write a generic method which returned both strings and ints either...

See here for more info on that kind of thing.

So what you have to do is return something that both types derive from (what they "have in common".)
As John Rasch says, you could return IList, (note the NON generic, so it's just a list of objects) or simply return it as an object. Unfortunately there is no way to preserve the type of the list.

Orion Edwards
+1  A: 

If you don't know the type you want until run-time, then generics are probably the wrong tool for the job.

If your function significantly changes behavior (like changing return type) based on an argument, then it should probably be two functions.

It looks like this function should not be generic, and should actually be two functions.

public void Main() {
    List<A> a = GetDataA();
}

public List<A> GetDataA() {
     List<A> a= new List<A>() 
     ...
     return a; 
}
public List<B> GetDataB() {
     List<B> b= new List<B>() 
     ...
     return b; 
}
Strilanc
+5  A: 

An alternative to being limited to returning a list of objects would be to either ensure that A and B derive from a common base type or implement a common interface, then return a list of that base type or interface. Include a constraint on the Generic method to that effect:-

List<ICommon> GetData<T>() where T: ICommon
{

}
AnthonyWJones
nifty, didn't know about the constraint 'where T: ICommon' +1
Jon Erickson
+3  A: 

Unless there's a specific reason that you can't specify the actual type ahead of time, you can just make the method itself generic:

public void Main() {
    List<A> a = GetData<A>();
}

public List<TType> GetData<TType>() {
     List<TType> list= new List<TType>();
     ...
     return list; 
}
CodeSavvyGeek
+1  A: 

I had to solve a similar problem recently where none of the proposed solutions was satisfactory; constraining the type parameter was not practical. Instead, I let the consumers of the method decide how to munge the data. For example, you can write a generic version of String.Split() that returns a strongly typed List, so long as you tell it how to convert substrings into T's.

Once you are willing to shift responsibility up the call stack (and get comfortable passing lambdas around), you can generalize this pattern arbitrarily. For instance, if the way you GetData() varies (as some responses apparently assume), you can hoist that function into the caller's scope as well.

Demo:

static void Main(string[] args)
{
    var parseMe = "Hello world!  1, 2, 3, DEADBEEF";

    // Don't need to write a fully generic Process() method just to parse strings -- you could 
    // combine the Split & Convert into one method and eliminate 2/3 of the type parameters
    List<string> sentences = parseMe.Split('!', str => str);
    List<int> numbers = sentences[1].Split(',', str => Int32.Parse(str, NumberStyles.AllowHexSpecifier | NumberStyles.AllowLeadingWhite));

    // Something a little more interesting
    var lettersPerSentence = Process(sentences,
                                     sList => from s in sList select s.ToCharArray(),
                                     chars => chars.Count(c => Char.IsLetter(c)));
}

static List<T> Split<T>(this string str, char separator, Func<string, T> Convert)
{       
    return Process(str, s => s.Split(separator), Convert).ToList();
}

static IEnumerable<TOutput> Process<TInput, TData, TOutput>(TInput input, Func<TInput, IEnumerable<TData>> GetData, Func<TData, TOutput> Convert)
{
    return from datum in GetData(input)
           select Convert(datum);
}

Functional programming gurus will probably yawn at this exploration: "you're just composing Map a few times." Even C++ guys might claim it's an example where template techniques (i.e. STL transform() + functors) require less work than generics. But as someone who primarily does C# it was nice to find a solution that preserved both type safety and idiomatic language usage.

Richard Berg