将对象数组的对象数组转换为对象的二维数组
Posted
技术标签:
【中文标题】将对象数组的对象数组转换为对象的二维数组【英文标题】:Convert an object array of object arrays to a two dimensional array of object 【发布时间】:2013-06-23 17:51:58 【问题描述】:我有一个第三方库返回一个对象数组的对象数组,我可以将其填充到一个对象[]中:
object[] arr = myLib.GetData(...);
结果数组由 object[] 条目组成,因此您可以将返回值视为某种记录集,其中外部数组表示行,而内部数组包含可能未填充某些字段的字段值(a锯齿状阵列)。要访问各个字段,我必须像这样投射:
int i = (int) ((object[])arr[row])[col];//access a field containing an int
现在我很懒,我想访问这样的元素:
int i = (int) arr[row][col];
为此,我使用以下 Linq 查询:
object[] result = myLib.GetData(...);
object[][] arr = result.Select(o => (object[])o ).ToArray();
我尝试使用像 object[][] arr = (object[][])result;
这样的简单强制转换,但失败并出现运行时错误。
现在,我的问题:
有更简单的方法吗?我感觉有些 漂亮的演员应该可以解决问题吗? 我也担心性能 因为我必须重塑大量数据只是为了节省一些演员,所以我 想知道这是否真的值得?编辑:
谢谢大家的快速解答。
@James:我喜欢你的回答将罪魁祸首包裹在一个新类中,但缺点是我在接收源数组时总是需要进行 Linq 包装,并且索引器需要 row 和 col 值int i = (int) arr[row, col];
(我需要像object[] row = arr[row];
一样获得完整的行,抱歉,一开始没有发布)。
@Sergiu Mindras:和 James 一样,我觉得扩展方法有点危险,因为它适用于所有 object[]
变量。
@Nair:我为我的实现选择了你的答案,因为它不需要使用 Linq 包装器,我可以使用 int i = (int) arr[row][col];
访问两个单独的字段或使用 object[] row = arr[row];
访问整行
@quetzalcoatl 和@Abe Heidebrecht:感谢Cast<>()
的提示。
结论:我希望我可以同时选择 James 和 Nair 的答案,但正如我上面所说,Nair 的解决方案给了我(我认为)最好的灵活性和性能。 我添加了一个函数,该函数将使用上述 Linq 语句“展平”内部数组,因为我还有其他需要使用这种结构的函数。
这是我(大致)实现它的方式(取自 Nair 的解决方案:
公共类 CustomArray 私有对象[] 数据; 公共CustomArray(对象[] arr) 数据=arr;
//get a row of the data
public object[] this[int index]
get return (object[]) data[index];
//get a field from the data
public object this[int row, int col]
get return ((object[])data[row])[col];
//get the array as 'real' 2D - Array
public object[][] Data2D()
//this could be cached in case it is accessed more than once
return data.Select(o => (object[])o ).ToArray()
static void Main()
var ca = new CustomArray(new object[]
new object[] 1,2,3,4,5 ,
new object[] 1,2,3,4 ,
new object[] 1,2 );
var row = ca[1]; //gets a full row
int i = (int) ca[2,1]; //gets a field
int j = (int) ca[2][1]; //gets me the same field
object[][] arr = ca.Data2D(); //gets the complete array as 2D-array
所以 - 再次 - 谢谢大家!使用这个网站总是一种真正的乐趣和启发。
【问题讨论】:
什么是运行时错误? 这里最昂贵的操作是从object
到int
(和其他类型)的拆箱,这似乎是不可避免的,因为你的lib 只返回object[]
。你确定它不提供类型化接口吗?
什么是 var[] arr = myLib.GetData(...);在这种情况下给你?
@Andre:返回的数据由不同的类型组成,而且,不,没有类型化接口,因为该函数基本上返回一个select语句的结果,该语句可以包含许多不同类型的字段。
【参考方案1】:
您可以创建一个包装类来隐藏丑陋的铸造,例如
public class DataWrapper
private readonly object[][] data;
public DataWrapper(object[] data)
this.data = data.Select(o => (object[])o ).ToArray();
public object this[int row, int col]
get return this.data[row][col];
用法
var data = new DataWrapper(myLib.GetData(...));
int i = (int)data[row, col];
还有机会使包装器通用,例如DataWrapper<int>
,但是,我不确定您的数据集合是否都是同一类型,返回 object
使其足够通用,以便您决定需要什么数据类型转换。
【讨论】:
一个想法:使用您当前的解决方案,每次用户调用data[1, 1]
,都会计算一次拆箱。那么,为什么不使用提供的代码 OP 将 object[]
转换为 object[][]
?
我会争论那个拆箱。如果项目被多次阅读,它实际上会加快整体使用速度。但是,如果这组项目只被读取一次并立即处理,则预拆箱将影响性能,可能会导致更高的内存使用量而没有真正的收益。考虑从数据库中获取数据时动态生成的数据流。迭代并缓存数百万个object[]
,只是为了不将它们拆箱两次..?这是一个应该严格针对具体用例量身定制的优化。请不要建议“仅仅因为它更好”。
@Quetzalcoatl 这是一个公平的观点,但是,假设 OP 正在 将读取所有信息,那么这可能是正确的方法。让我更新解决方案,使其在两种情况下都很灵活......
我的意思是读取一次(不那么值得努力)与多次读取(值得努力 N 次)。我注意到顶层数据对象是object[]
,所以所有数据都已经在内存中,但是缓存有效地使顶层数组的内存加倍。这是我想添加的唯一警告!正如我已经写过的,我喜欢这个解决方案。
@Quetzalcoatl "但是,如果这组项目只被读取一次并立即处理,那么预拆箱会影响性能" - 如果 all 至少在预先拆箱后才能读取这些项目会更好吗?否则你会拆箱 per index.【参考方案2】:
几乎没有类似的答案发布了类似的东西。仅当您想像
一样访问时,这才有所不同int i = (int) arr[row][col];
展示想法
public class CustomArray
private object[] _arr;
public CustomArray(object[] arr)
_arr = arr;
public object[] this[int index]
get
// This indexer is very simple, and just returns or sets
// the corresponding element from the internal array.
return (object[]) _arr[index];
static void Main()
var c = new CustomArray(new object[] new object[] 1,2,3,4,5 , new object[] 1,2,3,4 , new object[] 1,2 );
var a =(int) c[1][2]; //here a will be 4 as you asked.
【讨论】:
【参考方案3】:(1) 这可能可以使用dynamic
关键字以简明扼要的形式完成,但您将使用编译时检查。但是考虑到你使用 object[],这是一个不小的代价:
dynamic results = obj.GetData();
object something = results[0][1];
不过我没有用编译器检查过。
(2) 代替Select(o => (type)o)
有一个专用的Cast<>
函数:
var tmp = items.Select(o => (object[])o).ToArray();
var tmp = items.Cast<object[]>().ToArray();
它们几乎相同。我猜 Cast 会快一点,但我也没有检查过。
(3) 是的,以这种方式重塑会在一定程度上影响性能,主要取决于项目的数量。您拥有的元素越多,影响就越大。这主要与 .ToArray 相关,因为它将枚举所有项目并创建一个额外的数组。考虑一下:
var results = ((object[])obj.GetData()).Cast<object[]>();
这里的'结果'是IEnumerable<object[]>
类型,不同的是它会被延迟枚举,所以对所有元素的额外迭代没有了,临时的额外数组也没有了,而且开销也很小——类似手动转换每个元素,无论如何你都会这样做..但是 - 你失去了索引最顶层数组的能力。你可以在它上面循环/foreach
,但是你不能索引/[123]
它。
编辑:
就整体性能而言,James 的包装方式可能是最好的。我最喜欢它的可读性,但这是个人意见。其他人可能更喜欢 LINQ。但我喜欢它。我建议 James 的包装。
【讨论】:
【参考方案4】:你可以使用扩展方法:
static int getValue(this object[] arr, int col, int row)
return (int) ((object[])arr[row])[col];
并通过
检索int requestedValue = arr.getValue(col, row);
不知道 arr[int x][int y] 语法。
编辑
感谢詹姆斯的观察
您可以使用可为空的 int,这样在转换时就不会出现异常。
所以,方法会变成:
static int? getIntValue(this object[] arr, int col, int row)
try
int? returnVal = ((object[])arr[row])[col] as int;
return returnVal;
catch() return null;
并且可以通过
检索int? requestedValue = arr.getIntValue(col, row);
这样你得到一个可以为空的对象并且所有遇到的异常都强制返回null
【讨论】:
我认为这是对扩展方法的滥用。它应该足够通用以用于object[]
的任何实例,在这种情况下,您假设所有object[]
都将包含一个内部object[]
并且是int
类型。【参考方案5】:
您可以使用 LINQ Cast 运算符代替 Select...
object[][] arr = result.Cast<object[]>().ToArray()
这有点不那么冗长,但在性能方面应该几乎相同。另一种方法是手动完成:
object[][] arr = new object[result.Length][];
for (int i = 0; i < arr.Length; ++i)
arr[i] = (object[])result[i];
【讨论】:
以上是关于将对象数组的对象数组转换为对象的二维数组的主要内容,如果未能解决你的问题,请参考以下文章