Converting an array of type T to an array of type

2020-07-18 03:19发布

问题:

I am trying to accomplish something in C# that I do easily in Java. But having some trouble. I have an undefined number of arrays of objects of type T. A implements an interface I. I need an array of I at the end that is the sum of all values from all the arrays. Assume no arrays will contain the same values.

This Java code works.

ArrayList<I> list = new ArrayList<I>();
for (Iterator<T[]> iterator = arrays.iterator(); iterator.hasNext();) {
    T[] arrayOfA = iterator.next();
    //Works like a charm
    list.addAll(Arrays.asList(arrayOfA));
}

return list.toArray(new T[list.size()]);

However this C# code doesn't:

List<I> list = new List<I>();
foreach (T[] arrayOfA in arrays)
{
    //Problem with this
    list.AddRange(new List<T>(arrayOfA));
    //Also doesn't work
    list.AddRange(new List<I>(arrayOfA));
}
return list.ToArray();

So it's obvious I need to somehow get the array of T[] into an IEnumerable<I> to add to the list but I'm not sure the best way to do this? Any suggestions?

EDIT: Developing in VS 2008 but needs to compile for .NET 2.0.

回答1:

The issue here is that C# doesn't support co-variance (at least not until C# 4.0, I think) in generics so implicit conversions of generic types won't work.

You could try this:

List<I> list = new List<I>();
foreach (T[] arrayOfA in arrays)
{
    list.AddRange(Array.ConvertAll<T, I>(arrayOfA, t => (I)t));
}
return list.ToArray();

For anyone that strumbles across this question and is using .NET 3.5, this is a slightly more compact way of doing the same thing, using Linq.

List<I> list = new List<I>();
foreach (T[] arrayOfA in arrays)
{
    list.AddRange(arrayOfA.Cast<I>());
}
return list.ToArray();


回答2:

Edited for 2.0; it can become:

static void Main() {
    IEnumerable<Foo[]> source = GetUndefinedNumberOfArraysOfObjectsOfTypeT();
    List<IFoo> list = new List<IFoo>();
    foreach (Foo[] foos in source) {
        foreach (IFoo foo in foos) {
            list.Add(foo);
        }
    }
    IFoo[] arr = list.ToArray();
}

How about (in .NET 3.5):

I[] arr = src.SelectMany(x => x).Cast<I>().ToArray();

To show this in context:

using System.Collections.Generic;
using System.Linq;
using System;
interface IFoo { }
class Foo : IFoo { //  A implements an interface I
    readonly int value;
    public Foo(int value) { this.value = value; }
    public override string ToString() { return value.ToString(); }
}
static class Program {
    static void Main() {
        // I have an undefined number of arrays of objects of type T
        IEnumerable<Foo[]> source=GetUndefinedNumberOfArraysOfObjectsOfTypeT();
        // I need an array of I at the end that is the sum of
        // all values from all the arrays. 
        IFoo[] arr = source.SelectMany(x => x).Cast<IFoo>().ToArray();
        foreach (IFoo foo in arr) {
            Console.WriteLine(foo);
        }
    }
    static IEnumerable<Foo[]> GetUndefinedNumberOfArraysOfObjectsOfTypeT() {
        yield return new[] { new Foo(1), new Foo(2), new Foo(3) };
        yield return new[] { new Foo(4), new Foo(5) };
    }
}


回答3:

I assume you have tried

list.AddRange((I[])arrayOfA);

already?

EDIT In reply to your comment saying that my suggestion wouldn't work: I successfully ran this code just a minute ago:

using System;
using System.Collections.Generic;

namespace Tester
{
    class Program
    {
        private interface I
        {
            string GetValue();
        }

        private class A : I
        {
            private string value;

            public A(string v)
            {
                value = v;
            }

            public string GetValue()
            {
                return value;
            }
        }

        static void Main(string[] args)
        {
            List<I> theIList = new List<I>();

            foreach (A[] a in GetAList())
            {
                theIList.AddRange((I[])a);
            }

            foreach (I i in theIList)
            {
                Console.WriteLine(i.GetValue());
            }
        }

        private static IEnumerable<A[]> GetAList()
        {
            yield return new [] { new A("1"), new A("2"), new A("3") };
            yield return new [] { new A("4") };
        }
    }
}

Or did I just muss a requirement?



回答4:

Try adding a generic constraint

where T:I



回答5:

I'm guessing the problem here is that generics doesn't realize that T implements I. It might work to declare explicitly T : I.

Also you could do a for loop and add your T objects one at a time instead of using AddRange.



回答6:

The type structure of c# does not currently support this (treating the Foo<T> as a Foo<I> if X : I) this is termed covariance on I.

The underlying framework does, and c# 4.0 is adding support for it

As such explicit casts are required, Marc's answer being the simplest.



回答7:

In your C# code you are not adding the arrayOfA to the result list:

List<I> list = new List<I>();
foreach (T[] arrayOfA in arrays)
    list.AddRange(arrayOfA);

return list.ToArray();

However, if you are using .NET 3.5 you can do this with LINQ:

return (from arrayOfA in arrays
        from element in arrayOfA
        select element as I).ToArray();

Or using LINQ methods:

return arrays.SelectMany(arrayOfA => arrayOfA.Cast<I>()).ToArray();


回答8:

Arrays of reference objects are covariant in C# (the same is true for Java).

From the name, I guess that your T is a generic and not a real type, so you have to restrict it to a reference type in order to get the implicit conversion from T[] to I[].

Try this:

public static I[] MergeArrays<T,I>(IEnumerable<T[]> arrays) 
    where T:class,I
{
    List<I> list = new List<I>();
    foreach(T[] array in arrays){
        list.AddRange(array);
    }
    return list.ToArray();
}