【问题标题】:How can I cast IEnumerable<?> to IEnumerable<string>?如何将 IEnumerable<?> 转换为 IEnumerable<string>?
【发布时间】:2015-12-11 19:01:03
【问题描述】:

此问题已完全编辑。要查看原始版本,请查看编辑历史记录


我想我应该稍微解释一下我的情况和问题的背景。

我正在检查使用自定义属性注释的属性的类型。然后我得到该属性的值,但作为object

IEnumerable<object> propertiesValues = myType.GetProperties().Where(p => p.GetCustomAttributes(typeof(MyCustomAttribute)) != null);

一旦我有了这些值,我想在它们上调用我的方法Map,这会根据它们的类型将它们转换为不同的对象。这是因为我的数据库 API 需要这样做。

foreach(object value in propertiesValues)
{
     object mapped = Map(value);
     // ...
}

我需要将所有IEnumerable&lt;NumberType&gt; 值转换为IEnumerable&lt;string&gt;。所以我的第一个方法是:

public static object Map(object value)
{
    if(value is IEnumerable<short> || value is IEnumerable<int> || ....)
         return ((IEnumerable<object>) value).Cast<string>();
}

但是,这会引发运行时异常,例如,我无法将 IEnumerable&lt;int&gt; 转换为 IEnumerable&lt;object&gt;

【问题讨论】:

  • 我认为这是错误的return ((IEnumerable&lt;object&gt;) value).Cast&lt;string&gt;();应该不是return (IEnumerable&lt;object&gt;) value.Cast&lt;string&gt;();
  • @Tomaltach value 没有Cast 方法(它只是一个object)所以我需要将它转换为IEnumerable&lt;&gt;
  • IEnumerable&lt;int&gt; i = new List&lt;int&gt; { 1, 2, 3 }.AsEnumerable(); 然后IEnumerable&lt;object&gt; o = i.Cast&lt;object&gt;().AsEnumerable(); 有效。这是你要找的吗?
  • @RezaAghaei 看来我无法解释我的问题。我只想将伪装成objectIEnumerable&lt;?&gt; 投射到IEnumerable&lt;object&gt;。这可能吗?
  • @MatiasCicero:你的庆祝还为时过早。您根本不能使用 Cast 序列运算符进行这样的表示更改转换。您不能将 short 转换为字符串,因此您也不能将 CastIE&lt;short&gt; 转换为 IE&lt;string&gt;

标签: c# .net linq generics covariance


【解决方案1】:

你想要的特性叫做协方差,只有当类型参数都是引用类型时,C#才在IEnumerable&lt;T&gt;上支持它。也就是说,IEnumerable&lt;string&gt; 可以转换为IEnumerable&lt;object&gt;,但IEnumerable&lt;int&gt; 可能不会这样转换。

原因是:当你得到一个字符串序列时,那些字符串引用是已经合法的对象引用。但是 int 只有在装箱时才成为合法的对象引用;编译器在哪里知道插入装箱操作?它没有,所以转换是非法的。

要将IEnumerable&lt;int&gt; 转换为IEnumerable&lt;object&gt;,您必须通过投影显式进行装箱:

from number in items select (object)item

items.Select(item => (object) item)

那将是IEnumerable&lt;object&gt;,然后你可以做你喜欢的事情。

或者使用 Cast 序列操作符

items.Cast<object>()

它做同样的事情。或者使用它的查询形式:

from object item in items select item

或者简单地使用非泛型IEnumerable,如果实际上它是一个结构序列,它会给你一个装箱值序列。

我注意到,您通过Cast 扩展方法将IEnumerable&lt;object&gt; 转换为IEnumerable&lt;string&gt; 的计划注定要失败。这些项目将是盒装整数。装箱的整数不能拆箱为字符串。

您似乎在这里与类型系统作斗争,而不是使用它。也许你应该描述你真正的问题,而不是你试图围绕类型系统进行最终运行来解决它。

【讨论】:

  • 我完全同意最后一句话。
  • 我特别同意最后关于对抗类型系统的部分。我最近刚刚参加了一次代码审查,其中一位开发人员从处理中删除了大约 15 个看起来几乎完全一样的类(处理将检查类型参数,然后强制转换并传递给为每种潜在类型构建的单个类)
  • @moarboilerplate:对;任何时候您查询类型参数的类型参数时,您的代码都不再是通用。通用代码适用于所有可能的类型;这就是为什么它被称为“通用”。
  • @EricLippert 我不能同意。如果我的值是通过反射获得的(因为我想检查一个用自定义属性注释的属性)怎么办?我最终会得到一个object。在这种情况下我不能应用泛型。你会推荐什么?这正是我目前的情况:(
  • @MatiasCicero:不,不是。我是说不要使用IE&lt;T&gt; 根本。你注意到你身处一个没有泛型的世界;好的,那么你为什么要使用泛型?
【解决方案2】:

你为什么不这样做

var source = ... /// IEnumerable<???>
var destination = source.Select(item => item.ToString());

你也可以更新Map的签名:

object Map<TItem, TSource>(TSource source) 
  : where TSource : IEnumerable<TItem>

object Map<TItem>(IEnumerable<TItem> source) 

【讨论】:

  • 没那么简单。正如我所说,我的Map 方法有点复杂,所以我想用那个方法来实现这一点。我也不知道在运行时我将传递给Map,因此if (type is xx) 条件
  • 还是不够好。 Map 应该接受任何参数,我不想为 IEnumerable&lt;&gt; 创建约束。另外,我在编译时不知道参数的类型,所以约束是不可能的。
  • @MatiasCicero 我觉得你为Map 的设计有缺陷并且做得太多。
  • @MatiasCicero 你可以添加重载让编译器帮助你。
  • 编译时我不知道参数的类型。我无法创建重载。
猜你喜欢
  • 2011-06-11
  • 1970-01-01
  • 2018-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-23
  • 1970-01-01
相关资源
最近更新 更多