25

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

recursive
  • 83,943
  • 34
  • 151
  • 241
stevenrosscampbell
  • 643
  • 1
  • 11
  • 20

10 Answers10

36

Use IList instead of List<T>.

John Rasch
  • 62,489
  • 19
  • 106
  • 139
14

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
  • 187,081
  • 35
  • 232
  • 306
11

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.

Community
  • 1
  • 1
Orion Edwards
  • 121,657
  • 64
  • 239
  • 328
8

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; 
}
Kevin Kibler
  • 13,357
  • 8
  • 38
  • 61
7

You could do something like:

public void Main()
{
    List<int> a = GetData<int>();
    List<string> b = GetData<string>();
}

public List<T> GetData<T>()
{
    var type = typeof(T);
    if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        type = type.GenericTypeArguments[0];
    }

    if (type == typeof(int))
    {
        var a = new List<int> { 1, 2, 3 };
        return a.Select(v => v != null ? (T)Convert.ChangeType(v, type) : default(T)).ToList();
    }
    else if (type == typeof(string))
    {
        var b = new List<string> { "a", "b", "c" };
        return b.Select(v => v != null ? (T)Convert.ChangeType(v, type) : default(T)).ToList();
    }
}

Maybe you can modify that to your needs.

Marc Selman
  • 752
  • 1
  • 8
  • 14
5

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
  • 112,242
  • 44
  • 136
  • 174
2

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
  • 20,629
  • 2
  • 66
  • 86
2

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; 
}
Craig Gidney
  • 17,763
  • 5
  • 68
  • 136
0

A solution is to encapsulate the data in a container that will work as a client in the Visitor Pattern.

First some interface matching the pattern:

/// <summary>
/// The Client
/// </summary>
interface IDataContainer
{
    void AcceptDataProcessor(IDataProcessor dataProcessor);
}

/// <summary>
/// The Visitor.
/// </summary>
interface IDataProcessor
{
    void WorkOn<TData>(List<TData> data);
}

Then an implementation of each:

class DataContainer<TData> : IDataContainer
{
    readonly List<TData> list;

    public DataContainer(List<TData> list)
    {
        this.list = list;
    }

    public void AcceptDataProcessor(IDataProcessor dataProcessor)
    {
        dataProcessor.WorkOn(list); // Here the type is known.
    }
}

class PrintDataProcessor : IDataProcessor
{
    public void WorkOn<TData>(List<TData> data)
    {
        // print typed data.
    }
}

Then a use of it:

public void Main()
{
    var aContainer = GetData("A");
    var bContainer = GetData("B");

    var printProccessor = new PrintDataProcessor();

    aContainer.AcceptDataProcessor(printProccessor); // Will print A data
    bContainer.AcceptDataProcessor(printProccessor); // Will print B data
}


public IDataContainer GetData(string listType)
{
    if (listType == "A")
        return new DataContainer<A>(new List<A>());
    if (listType == "B")
        return new DataContainer<B>(new List<B>());
    throw new InvalidOperationException();
}

The idea is that DataContainer know the underlying type but does'nt expose it.

  • It doesn't expose it so GetData can contains any kind of data (but it's hided).
  • DataContainer know the underlying type, it is in charge to call the good typed method of the worker: dataProcessor.WorkOn(list);

It's a powerful pattern but cost a lot in terms of code.

Orace
  • 7,822
  • 30
  • 45
0

I know its way too late but I came here with the same issue and this is how i worked it out using interfaces. Thought I'll post it for the benefit of others

 public interface IEntity
    {
        int ID
        {
            get;
            set;
        }
    }

public class Entity2:IEntity
    {
        public string Property2;

        public int ID
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }
    }

Similarly for Entity1.

Now in my class (my business layer) I have this method

 public List<IEntity> GetEntities(Common.EntityType entityType)
           {
               List<IEntity> entities = new List<IEntity>();

               switch (entityType)
               {
                   case Common.EntityType.Accounts:
                       Entity1 entity1 = new Entity1();
                       entity1.Property1 = "AA";
                       entities.Add(entity1);

                       break;
                   case Common.EntityType.Brands:
                       Entity2 entity2 = new Entity2();
                       entity2.Property2 = "AA";
                       entities.Add(entity2);

                       break;
                   default:
                       break;
               }

 return entities;
       }

From the UI, I would call it like this

BusinessClass b = new BusinessClass();
        List<IEntity> a = b.GetEntities(Common.EntityType.Accounts);

Hope this helps

hangar18
  • 638
  • 1
  • 9
  • 21