views:

362

answers:

8

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  A: 

Try adding a generic constraint

where T:I

Noel Kennedy
+1  A: 

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.

Brian Reiter
+7  A: 

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) };
    }
}
Marc Gravell
Out of curiousity, what's the purpose of the SelectMany there? You beat me by seconds on the .Cast, so +1 from me :-)
Dan F
The SelectMany is effectively concatenating the elements of the arrays.
Jon Skeet
Awesomecake, I think I'll add that one to the list of cool things I didn't know about LINQ. Ta fellas
Dan F
Sorry, should have specified that it needs to compile for 2.0. But as Dan says some very cool LINQ stuff there
Adrian Hope-Bailie
The Cast<IFoo>() is unnecessary. It would be necessary if you were assigning to IEnumerable<IFoo> instead of IFoo[] (until C# 4.0, when it becomes unnecessary there also).
Daniel Earwicker
(Although of course it really should have been necessary for arrays as they are mutable!)
Daniel Earwicker
It is required for arrays of value-type objects ;-p
Marc Gravell
+3  A: 

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?

Thorsten Dittmar
:) Yes that's not gonna work in any language
Adrian Hope-Bailie
I fail to see why the above should not work. I've edited my answer and posted some sample code that works.
Thorsten Dittmar
No, can't see how that works because it certainly doesn't work on my example and yet your code compiles and runs fine.As far as I understood you can never cast from T[] to I[] even if T:I. Excuse me for doubting, I must apologise!
Adrian Hope-Bailie
No need to apologize! I'm just curious why your code doesn't work while my code does. I always thought being able to cast from T[] to I[] is part of polymorphism if T implements I? Also, I'm even doing the opposite all the time, for example casting from `DataRow[]` to `TypedDataSetRow[]`.
Thorsten Dittmar
Covariant array conversions "work" in that they are legal, but they are broken in that doing so creates situations in which type safety is compromised. They are legal in C# and Java; I wish they had never been made legal in either. See my article on the subject: http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx
Eric Lippert
There is a suggestion above that the reason this doesn't work in my case is that T is infact a struct (or value type). I am assuming this to be correct? Perhaps you can confirm? (Sorry I didn't mention that in the original question)
Adrian Hope-Bailie
+5  A: 

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();
Dan Herbert
Like that, it's very elegant. Had to give a few +1 but ended up using this. Thanks. Still confused about Throsten's answer. Not sure why it won't work in my example yet his code works fine?
Adrian Hope-Bailie
Adrian: perhaps your T is a value type? Array type covariance only works on reference types. In your example, try casting like this: (IEnum<I>)(I[])arrayOfA -- does that work?
Eric Lippert
Aha! Bingo. T is a value type, I guess I should have specified that up front. Learning lots of new stuff today. Unfortunately though your suggestion hasn't helped, still sticking with the answer above.
Adrian Hope-Bailie
+1  A: 

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.

ShuggyCoUk
A: 

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();
Bojan Resnik
Yes, well spotted. I think everyone got the gist of the question none-the-less. Fixed and a +1 for you for sharp eyes :) (Unf no LINQ so couldn't use your solution.)
Adrian Hope-Bailie
The non-LINQ code should work for you, none the less, as Rasmus says, arrays of reference objects are covariant in C#.
Bojan Resnik
I am assuming the non-LINQ code doesn't work for the same reason I cant cast form T[] to I[]. At this stage it appears this is because T is actually a value type. BAck to school for me!
Adrian Hope-Bailie
A: 

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();
}
Rasmus Faber