【问题标题】:How to cast an IEnumerable<object> to an IEnumerable<runtime type>如何将 IEnumerable<object> 转换为 IEnumerable<runtime type>
【发布时间】:2015-08-18 19:54:50
【问题描述】:

我正在尝试完成以下任务。

假设我有这个数据模型:

public class Article
{
     public ICollection<string> Tags { get; set; }
}

这些标签是从数据库中检索的。我的数据库的 API 将它们作为 List&lt;object&gt; 返回给我。

因此,我需要将List&lt;object&gt; 转换为实现ICollection&lt;string&gt; 的东西。

我知道 LINQ Cast&lt;T&gt;() 方法将其元素转换为给定类型并返回转换后的 IEnumerable&lt;T&gt;

但是,我不能使用Cast&lt;string&gt;(),因为这总是会将我的List&lt;object&gt; 转换为IEnumerable&lt;string&gt;,而不会为具有ICollection&lt;double&gt; 属性(或任何其他类型)的模型提供任何选项。

我可以使用反射并获取泛型类型参数:

Type genericArg = collectionType.GetGenericArguments().First();

但这会给我留下一个运行时Type,我不能将其用作Cast&lt;genericArg&gt;()

如何将IEnumerable&lt;object&gt; 转换为动态TypeIEnumerable?。

我应该注意,我的模型上不允许使用复杂类型,所以像:

public ICollection<Tag> Tags { get; set; }

不会发生。我只处理原始类型。

【问题讨论】:

  • 我不明白 Cast() 在每种情况下都有什么问题。
  • 如果您不知道这些值是什么类型,您将如何使用它们?
  • “我的数据库的 API 将它们作为 List&lt;object&gt; 返回给我”似乎修复 API 可以解决您的问题。为什么它不能返回不同的集合类型?
  • 我不能使用任何通用方法,因为直到运行时我才知道T。这种映射方法将被许多模型调用,有时我必须转换为IEnumerable&lt;string&gt;,有时必须转换为IEnumerable&lt;double&gt;
  • 如果您在运行时之前不知道它拥有什么类型,为什么获得一个类型化的IEnumerable&lt;T&gt; 会对您有所帮助?你到底想完成什么?

标签: c# .net linq generics casting


【解决方案1】:

你对投射有一个基本的误解。

强制转换操作的结果类型必须在编译时知道。¹

考虑以下示例:

string a = "abc";
object b = (object)a;
string c = (string)b;

abc运行时类型是相同的。这是string编译时类型是不同的。强制转换仅与编译时类型相关。

因此,您的问题的答案

如何将IEnumerable&lt;object&gt; 转换为IEnumerable&lt;runtime type&gt;

是:你没有。强制转换对运行时类型没有意义。


也就是说,让我为您的实际问题提供一个解决方案:假设您有一个 IEnumerable&lt;object&gt; values、一个 Type myTargetType 并希望创建一个包含这些值的 List&lt;typeof(myTargetType)&gt;

首先,您使用反射创建列表:

var listType = typeof(List<>).MakeGenericType(myTargetType);
IList myList = (IList)Activator.CreateInstance(listType);

然后你填写列表:

foreach (var item in values)
{
    myList.Add(item);
}

显然,如果values 的条目不是 的运行时类型myTargetTypeAddthrow an ArgumentException


¹结果类型可以是泛型类型,但generic type parameters have to be specified at compile time as well

【讨论】:

  • “强制转换操作的结果类型必须在编译时知道” - 不正确 - 您可以强制转换为泛型类型 - 这就是 Linq 的 Cast&lt;T&gt; 的工作原理。
  • @DStanley:是的,但泛型类型也必须在编译时知道:如果您提供具体类型或其他泛型类型,则只能调用Cast&lt;T&gt;。 (并且,在后一种情况下,可以为 that 类型创建相同的参数。在调用链的末尾必须有一个具体的类型。)你不能用运行时类型调用它.我会在我的问题中澄清这一点以避免误解。
【解决方案2】:

我相信 System.Convert 有你需要的东西:

Type genericArg = collectionType.GetGenericArguments().First();
foreach(var obj in collection) {
    yield return Convert.ChangeType(obj, genericArg);
}

【讨论】:

  • 如果您假设他的 DB 层处理获取正确的类型,那么可以。该解决方案允许他指定目标集合类型,只要存在转换规则,它就会起作用。
【解决方案3】:

Enumerable.Cast&lt;T&gt;(this IEnumerable source) 通常是您要查找的内容。如果需要不同的变体,可以使用反射自己关闭泛型:

class Program
{
    static void Main(string[] args)
    {
        var source = new List<object> {
            "foo",
            "bar",
            "baz"
        };

        var type = typeof(string); // or however you find out the type

        var castMethod = typeof(Enumerable)
            .GetMethod("Cast").MakeGenericMethod(
            new[] {
                type
            });

        var result = (IEnumerable<string>)
            castMethod.Invoke(null, new object[] {source});

        foreach (var str in result)
        {
            Console.WriteLine(str.ToUpper());
        }
    }
}

另一个问题是从一个List&lt;T&gt; 转换为另一个没有意义——泛型参数是不变量,因为集合是可读写的。 (由于历史原因,数组允许进行一些此类转换。)但是,如果您只是阅读,则从 Cast 返回的 IEnumerable&lt;T&gt; 就足够了。

【讨论】:

  • 问题是直到运行时我才知道T的类型
  • @MatiCicero 如果需要,您可以在运行时通过反射关闭它。
  • @MatiCicero 我添加了一个在运行时关闭泛型的示例。
【解决方案4】:

您需要实现一个通用方法,该方法从您的数据库 api 获取结果并根据您的模型返回适当的集合,如下所示:

private ICollection<T> RetrieveTags()
{
      // Get tags using database api

      return tags.Cast<T>();
 }

然后根据需要调用该方法获取模型,例如:

ICollection<int>  t1 = RetrieveTags<int>();
ICollection<string>  t2 = RetrieveTags<string>();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多