返回未知的泛型列表<T>。

25

谢谢任何帮助。

如何从一个方法返回未知的 Generic.List 类型。

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;

   }
}
在下面的示例中,我收到了一个类似于以下错误:无法将List<A>转换为List<T> 这种情况可能吗?错误发生在代码的“return a;”行。
另外,我需要做什么来确保不会在该行出现错误?
List<A> a= GetData("A");   

谢谢,Steven

10个回答

36

使用IList代替List<T>


4
同意那应该是最终目标,但这并没有真正帮助他解决眼前的问题。 - Joel Coehoorn

14
作为一种替代方法,不局限于返回对象列表,可以确保A和B派生自一个共同的基础类型或实现一个共同的接口,然后返回该基础类型或接口的列表。请在泛型方法中包含一个对该效果的约束条件:
List<ICommon> GetData<T>() where T: ICommon
{

}

nifty, 不知道 'where T: ICommon' 约束条件 +1 - Jon Erickson

11
你不能直接返回一个 List<T> 像这样。
为什么呢?基本上因为 List<A>List<B> (或者 List<string> vs List<int> 这种情况也一样)被认为是 2 个完全不相关的类。
就像你不能从一个声明返回 int 的函数中返回一个 string,你也不能从一个声明返回字符串列表的函数中返回整数列表。这里的 <T> 有点误导人。你也无法编写一个通用方法,它同时返回字符串和整数...
关于那种事情,请在此处查看更多信息。
因此,你必须返回两种类型都派生自的东西(它们“共同拥有的”)。如John Rasch 所说,你可以返回 IList(注意,非泛型,所以它只是一个 object 的列表),或者简单地将其作为 object 返回。不幸的是,没有办法保留列表类型。

8

除非有特定的原因不能事先指定实际类型,否则可以将方法本身设为通用型:

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

public List<TType> GetData<TType>() {
     List<TType> list= new List<TType>();
     ...
     return list; 
}

7
您可以这样做:
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();
    }
}

也许您可以根据自己的需要进行修改。

5

根据Orion的答案,我做了修改,加入了AnthonyWJones建议的约束条件

你应该有一个接口/抽象类,A和B都继承自它

    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;
    }

你忘记了类型参数-我已经为你添加了它。 - Joel Coehoorn
1
如果(typeof(T) == typeof(A))则会让悲伤的熊猫难过 :-( - Orion Edwards
@Orion,我该如何改进它?我同意它看起来很丑,但我的想法已经实现了。 - Jon Erickson
@Jon 访问者模式会让它变得更好。 - Orace

2
最近我遇到了一个类似的问题,但是提出的解决方案都不太令人满意;限制类型参数也不实际。相反,我让方法的使用者决定如何处理数据。例如,你可以编写一个泛型版本的String.Split(),它返回一个强类型的List,只要你告诉它如何将子字符串转换为T类型。
一旦你愿意将责任转移至调用堆栈上(并且习惯于传递lambda表达式),你可以任意地推广这种模式。例如,如果你的GetData()方式有所变化(正如一些响应所假定的那样),你也可以将该函数提升到调用者的范围内。
演示:
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);
}

函数式编程专家可能会对这个探索感到厌倦:“你只是组合了几次 Map。” 即使 C++ 程序员可能会认为这是一个例子,其中模板技术(即 STL transform() + 函数对象)比泛型需要更少的工作。但作为一个主要使用 C# 的人,找到一种既保留类型安全性又符合惯用语言用法的解决方案还是很不错的。


2

如果直到运行时才知道所需类型,那么泛型可能不是最合适的工具。

如果您的函数根据参数显著更改行为(例如更改返回类型),则应该拆分为两个函数。

看起来这个函数不应该是泛型的,而实际上应该是两个函数。

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; 
}

0

我知道现在已经太晚了,但我也遇到了同样的问题,我使用接口解决了它。想着为了其他人的利益,我把解决方法发在这里。

 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();
            }
        }
    }

同样地,对于实体1也是如此。

现在在我的类(我的业务层)中,我有这个方法。

 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;
       }

从用户界面,我会这样调用它

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

希望这能有所帮助


0
一个解决方案是将数据封装在一个容器中,该容器将作为访问者模式中的客户端工作。
首先,需要一些与该模式匹配的接口:
/// <summary>
/// The Client
/// </summary>
interface IDataContainer
{
    void AcceptDataProcessor(IDataProcessor dataProcessor);
}

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

然后是每个实现:

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

然后就可以使用它:

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();
}

这个想法是 DataContainer 知道底层类型但不会暴露它。

  • 它不会暴露它,所以 GetData 可以包含任何类型的数据(但它被隐藏了)。
  • DataContainer 知道底层类型,它负责调用工作程序的正确类型方法:dataProcessor.WorkOn(list);

这是一个强大的模式,但在代码方面成本很高。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接