问题 将对象数组的对象数组转换为对象的二维数组


我有一个第三方库返回一个对象数组的对象数组,我可以把它放入一个对象[]:

object[] arr = myLib.GetData(...);

结果数组由object []条目组成,因此您可以将返回值视为某种记录集,其中外部数组表示行,而内部数组包含可能未填充某些字段的字段值(锯齿状数组) 。要访问各个字段,我必须像:

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:像詹姆斯一样,我觉得扩展方法有点危险,因为它适用于所有人 object[] 变量。
@Nair:我为我的实现选择了你的答案,因为它不需要使用Linq包装器,我可以使用它来访问两个单独的字段 int i = (int) arr[row][col]; 或使用整行 object[] row = arr[row];
@quetzalcoatl和@Abe Heidebrecht:谢谢你的提示 Cast<>()

结论: 我希望我可以选择James'和Nair的答案,但正如我上面所说,Nair的解决方案让我(我认为)具有最佳的灵活性和性能。 我添加了一个函数,它将使用上面的Linq语句“展平”内部数组,因为我还有其他需要使用这种结构的函数。

以下是我(大致)实现它的方式(取自Nair的解决方案:

公共类CustomArray         {             私有对象[]数据;             public CustomArray(object [] arr)             {                 data = 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
        }

    }

所以 - 再次 - 谢谢大家!使用这个网站总是一种真正的乐趣和启示。


2704
2018-06-26 14:16


起源

什么是运行时错误? - Karel Frajták
这里最昂贵的操作是取消装箱 object 至 int (和其他类型),这似乎是不可避免的,因为你的lib只返回 object[]。你确定它没有提供打字的界面吗? - Andre Calil
var [] arr = myLib.GetData(...)是什么;在这种情况下给你? - Bit
@Andre:返回的数据由不同的类型组成,并且不,没有类型化的接口,因为该函数基本上返回可以包含许多不同类型字段的select语句的结果。 - AstaDev


答案:


几乎没有类似的答案张贴,它做了类似的事情。这只有你想要的时候才有所不同

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
2018-06-26 14:30





您可以创建一个包装类来隐藏丑陋的转换,例如

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 保持通用性足以让您决定需要什么样的数据类型。


7
2018-06-26 14:24



先生,这是隐藏肮脏工作的优雅原因。 +1 - Andre Calil
一个想法:使用您当前的解决方案,每次用户调用 data[1, 1],将计算拆箱。所以,为什么你不提前转换 object[] 至 object[][] 使用OP提供的代码? - Andre Calil
@AndreCalil是的好喊,让我更新。 - James
我会争论拆箱。如果多次阅读这些项目,实际上会加快整体使用。但是,如果这些项目的集合只是被读取一次并立即处理,则预先取消装箱将达到性能,可能会有更高的内存使用量而无法获得实际收益。考虑从数据库中提取数据时动态生成的数据流。迭代并缓存数百万 object[]只是不要将他们拆箱两次..?这是一个应根据具体用例严格定制的优化。请不要暗示“只是因为它更好”。 - quetzalcoatl
@Quetzalcoatl这是一个公平的观点,然而,假设OP 是 要阅读所有信息,那么它可能是正确的方法。让我更新解决方案,使其适用于两种情况...... - James


(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] 它。

编辑:

詹姆斯的包装方式在整体表现方面可能是最好的。我最喜欢它的可读性,但这是个人观点。其他人可能更喜欢LINQ。但我喜欢它。我建议詹姆斯的包装纸。


1
2018-06-26 14:28





您可以使用扩展方法:

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


1
2018-06-26 14:29



我认为这是滥用扩展方法。它应该足够通用,可以在任何实例上使用 object[],在这种情况下,你假设所有 object[] 将包含内在 object[] 并且是类型 int。 - James


您可以使用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];

0
2018-06-26 14:27