为什么我不能将DateTime[]转换为object[]?

12

看起来我可以将DateTime转换为object,那么为什么不能将数组DateTime[]强制转换为object[]?我知道这与值类型和引用类型有关,但拆箱不允许我这样做吗?


这是有可能的,所以你一定是在错误的尝试中。 - Kirk Broadhurst
@Kirk: 在C#中是不行的。或者在CLR上的任何语言中都不行。没有这样的引用转换。 - Eric Lippert
@Eric:我误解了问题!我的意思是,你可以通过将每个DateTime转换为对象来将DateTime[]转换为object[],但当然你是正确的——数组本身不能被转换。 - Kirk Broadhurst
可能是重复的问题:无法将值类型数组转换为params object[] - nawfal
6个回答

13

数组协变性只适用于引用类型的数组。因为DateTime是值类型,所以你不能将DateTime[]分配给object[]变量。你需要显式创建一个对象数组并复制值。换句话说,创建一个类型为object[]的新数组实例。

有很多方法可以实现这个操作。使用CopyTo()的简单方法应该就足够了。

DateTime[] x = new DateTime[] { ... };
object[] y = new object[x.Length];
x.CopyTo(y, 0);

我进行了一些测试。可能不是最好的方法,但它应该可以给出一个好的想法,如果使用适当的分析器会怎样。

class Program
{
    static void Main(string[] args)
    {
        var now = DateTime.Now;
        var dates = new DateTime[5000000];
        for (int i = 0; i < dates.Length; i++)
            dates[i] = now.AddSeconds(i);
        for (int i = 0; i < 5; i++)
        {
            Test("Test1", () =>
            {
                var result = new object[dates.LongLength];
                for (long l = 0; l < result.LongLength; l++)
                    result[l] = dates[l];
                return result;
            });
            Test("Test2", () =>
            {
                var result = new object[dates.LongLength];
                dates.CopyTo(result, 0);
                return result;
            });
            Test("Test3", () =>
            {
                var result = new object[dates.LongLength];
                Array.Copy(dates, result, dates.LongLength);
                return result;
            });
            Test("Test4", () =>
            {
                var result = Array.ConvertAll(dates, d => (object)d);
                return result;
            });
            Test("Test5", () =>
            {
                var result = dates.Cast<object>().ToArray();
                return result;
            });
            Test("Test6", () =>
            {
                var result = dates.Select(d => (object)d).ToArray();
                return result;
            });
            Console.WriteLine();
        }
    }

    static void Test<T>(string name, Func<T> fn)
    {
        var startMem = GC.GetTotalMemory(true);
        var sw = Stopwatch.StartNew();
        var result = fn();
        sw.Stop();
        var endMem = GC.GetTotalMemory(false);
        var diff = endMem - startMem;
        Console.WriteLine("{0}\tMem: {1,7}/{2,7} ({3,7})", name, startMem, endMem, diff);
        Console.WriteLine("\tTime: {0,7} ({1,7})", sw.ElapsedMilliseconds, sw.ElapsedTicks);
    }
}

技术规格:
Win7Pro x64,Core2Quad Q9550@ 2.83GHz,4GiB DDR2 1066(PC2-8500)
64位版本(32位版本大致相同,只是总体内存较少)

测试1   内存:40086256 / 200087360(160001104)
        时间:444(1230723)
测试2   内存:40091352 / 200099272(160007920)
        时间:751(2078001)
测试3   内存:40091416 / 200099256(160007840)
        时间:800(2213764)
测试4   内存:40091480 / 200099256(160007776)
        时间:490(1358326)
测试5   内存:40091608 / 300762328(260670720)
        时间:1407(3893922)
测试6   内存:40091672 / 300762328(260670656)
        时间:756(2092566)
测试1 内存:40091736 / 200099184(160007448) 时间:515(1425098) 测试2 内存:40091736 / 200099184(160007448) 时间:868(2404151) 测试3 内存:40091736 / 200099160(160007424) 时间:885(2448850) 测试4 内存:40091736 / 200099184(160007448) 时间:540(1494429) 测试5 内存:40091736 / 300762240(260670504) 时间:1479(4093676) 测试6 内存:40091736 / 300762216(260670480) 时间:746(2065095)
测试1 内存:40091736 / 200099168(160007432) 时间:500(1383656) 测试2 内存:40091736 / 200099160(160007424) 时间:781(2162711) 测试3 内存:40091736 / 200099176(160007440) 时间:793(2194605) 测试4 内存:40091736 / 200099184(160007448) 时间:486(1346549) 测试5 内存:40091736 / 300762232(260670496) 时间:1448(4008145) 测试6 内存:40091736 / 300762232(260670496) 时间:749(2075019)
测试1 内存:40091736 / 200099184(160007448) 时间:487(1349320) 测试2 内存:40091736 / 200099176(160007440) 时间:781(2162729) 测试3 内存:40091736 / 200099184(160007448) 时间:800(2214766) 测试4 内存:40091736 / 200099184(160007448) 时间:506(1400698) 测试5 内存:40091736 / 300762224(260670488) 时间:1436(3975880) 测试6 内存:40091736 / 300762232(260670496) 时间:743(2058002)

有趣的是,ConvertAll() 的表现与普通循环几乎相同。


谢谢你的解释和链接!我原以为.NET 4的协变性工作会涵盖这种情况,但看来并不是这样。 - Suraj
令人惊叹的测试。这基本上意味着Array.ConvertAll在性能方面非常出色(而且只有1行代码,因此比Test1方法更易读)。 - Suraj

4

你不能将 DateTime[] 强制转换为 object[],因为这样做是不安全的。所有相同长度的引用类型数组在内存中具有相同的布局。DateTime 是值类型,并且该数组是“扁平”的(未装箱)。你不能安全地将其强制转换为 object[],因为内存中的布局与 object[] 不兼容。


这并不是原因。例如,对于引用类型,数组允许方差,同样的“不安全”事情可能在编译时出现,并且在赋值时会在运行时抛出异常。 - Anthony Pegram
@Logan - 我不明白... "Uh oh" 这行代码应该是完全正常的。 - Suraj
@Anthony- 我确定我漏了什么……难道不是任何东西都可以赋值给一个对象吗?如果3是一个对象的数组,为什么会失败呢? - Suraj
@Jeff Mercado,我是在谈论我的第一条评论。对于造成的混淆,我感到抱歉。 - Anthony Pegram
@Jeff:说实话,你可以绕过编译器的限制并将某些值类型的数组强制转换为彼此。例如,即使C#编译器会试图阻止你,CLR仍允许将int[]转换为uint[]。实际上,CLR要求的是元素类型是赋值兼容的,而int与uint是赋值兼容的。 - Eric Lippert
显示剩余5条评论

3
如果您使用的是LINQ (.NET 3.5+), 您可以这样做:
DateTime[] dates = new DateTime[3];

dates[0] = new DateTime(2009, 01, 01);
dates[1] = new DateTime(2010, 01, 01);
dates[2] = new DateTime(2011, 01, 01);

object[] dates2 = Array.ConvertAll(dates, d => (object)d);

正如Jeff所指出的那样,你也可以在.NET 2.0中使用委托来实现类似的功能:

object[] dates3 = Array.ConvertAll(dates, 
                        delegate(DateTime d) { return (object)d; });

@Kev - 你认为Array.ConvertAll比Cast<object>().ToArray()更快吗? - Suraj
只是让你知道,Array.ConvertAll()也兼容.NET 2.0。虽然在这种情况下可能无法使用lambda表达式。 - Jeff Mercado
1
@SFun28 - 使用 Cast<object>().ToArray() 会遍历数组两次。一次是为了 Cast<object>(),另一次是为了 .ToArray()。而使用 .ConvertAll() 只需要遍历一次数组。 - Kev
@Kev:更正一下,它只被遍历了一次,尽管它创建了两个额外的枚举器,一个用于 Cast,另一个用于 ToArray 转换器。Cast 只是一个生成器,它不会将结果复制到新集合中,它只会在源被枚举时生成转换后的对象。ToArray 强制枚举并将结果放入数组中。所有非转换器(即所有以 To- 开头的操作)都是生成器。这有所不同。 - Jeff Mercado
1
@SFun:是的。我为此进行了一些内存和速度基准测试。我会将其添加到我的答案中,这样您就可以看到差异。 - Jeff Mercado
显示剩余4条评论

0

顺便说一下,你可以使用Array.Copy()来完成这个任务。

void Main()
{
    DateTime[] dates = new DateTime[] { new DateTime(2000, 1, 1), new DateTime (2000, 3, 25) };
    object[] objDates = new object[2];
    Array.Copy(dates, objDates, 2);

    foreach (object o in objDates) {
        Console.WriteLine(o);
    }
}

同时也可以通过LINQ完成:dates.Cast<object>().ToArray()。 - Suraj

0

请参考其他答案,了解为什么您无法这样做。

另一种选择是执行数组的深度复制。使用LINQ的示例:

DateTime[] dates = ...;
object[] objects = dates.Select(d => (object)d).ToArray();

或者 dates.Cast<object>().ToArray(),这就是我现在正在做的。 - Suraj
@SFun28 哦,是的,我忘了那个。 - Etienne de Martel

0

因为DateTime一个object,但DateTime的数组不是object数组。

值类型的数组与引用类型的数组不同,因此这两种类型的数组在根本上是不兼容的。值类型数组实际上包含值,引用类型数组只包含引用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接