返回未知的通用列表<T>

Posted

技术标签:

【中文标题】返回未知的通用列表<T>【英文标题】:return unknown Generic List<T> 【发布时间】:2009-02-26 00:11:58 【问题描述】:

感谢您的帮助。

如何从方法返回未知的 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&lt;A&gt; 转换为 List&lt;T&gt;

这可能吗?错误发生在'return a;'一行代码。 另外,我需要做些什么来确保在线上不会发生错误:

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

谢谢, 史蒂文

【问题讨论】:

【参考方案1】:

使用IList 代替List&lt;T&gt;

【讨论】:

同意这应该是最终目标,但这并不能真正帮助他解决眼前的问题。【参考方案2】:

限制返回对象列表的另一种方法是确保 A 和 B 从公共基类型派生或实现公共接口,然后返回该基类型或接口的列表。包括对通用方法的约束:-

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



【讨论】:

漂亮,不知道约束'where T: ICommon' +1【参考方案3】:

您不能像这样直接返回List&lt;T&gt;

为什么?基本上是因为List&lt;A&gt;List&lt;B&gt;(或List&lt;string&gt; vs List&lt;int&gt; 这是同一件事)被认为是2个完全独立的不相关的类。 正如您不能从声明为返回 int 的函数中返回 string 一样,您也不能从声明为返回整数列表的函数中返回字符串列表。这里的&lt;T&gt; 有点牵强。你也不能编写一个同时返回字符串和整数的通用方法......

有关此类事情的更多信息,请参阅here。

因此,您要做的就是返回两种类型都派生自的东西(它们“具有共同点”。) 作为John Rasch says,您可以返回IList,(注意NON 泛型,因此它只是objects 的列表)或简单地将其作为object 返回。不幸的是,没有办法保留列表的类型。

【讨论】:

【参考方案4】:

除非有特定原因无法提前指定实际类型,否则可以将方法本身设为泛型:

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


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

【讨论】:

【参考方案5】:

你可以这样做:

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

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

【讨论】:

【参考方案6】:

根据 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;
    

【讨论】:

您忘记了类型参数-为您添加了它。 if( typeof(T) == typeof(A) ) 让熊猫伤心:-( @Orion 我怎样才能做得更好?我同意它看起来很丑,但我的想法已经实现了。 @Jon 访客模式会让它变得更好。【参考方案7】:

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

如果您的函数基于参数显着改变行为(例如更改返回类型),那么它可能应该是两个函数。

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

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; 

【讨论】:

【参考方案8】:

我最近不得不解决一个类似的问题,其中提出的解决方案都不令人满意;约束类型参数是不切实际的。相反,我让方法的使用者决定如何处理数据。例如,您可以编写返回强类型 List 的 String.Split() 的通用版本,只要您告诉它如何将子字符串转换为 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() + functors)比泛型需要更少的工作的示例。但作为主要使用 C# 的人,很高兴找到一个既能保持类型安全又能保持惯用语言用法的解决方案。

【讨论】:

【参考方案9】:

我知道它为时已晚,但我来到这里时遇到了同样的问题,这就是我使用接口解决问题的方法。以为我会为了他人的利益而发布它

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

对于 Entity1 也是如此。

现在在我的班级(我的业务层)我有这个方法

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

希望对你有帮助

【讨论】:

【参考方案10】:

解决方案是将数据封装在一个容器中,该容器将作为Visitor Pattern 中的客户端

首先是一些匹配模式的接口:

/// <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知道底层类型,负责调用worker好的类型化方法:dataProcessor.WorkOn(list);

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

【讨论】:

以上是关于返回未知的通用列表<T>的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能返回一个通用的 'T' 来满足 Partial<T>?

Rust:从(仅)<T> 不同的函数返回通用结构

将 DataTable 转换为通用列表?

如何从 C# 中的通用 List<T> 中获取元素? [复制]

如何将通用 List<T> 转换为基于接口的 List<T>

C#从反射类型实例化通用列表[重复]