Преобразование массива типа T в массив типа I, где T реализует I в C#

Я пытаюсь сделать что-то на С#, что я легко делаю на Java. Но с некоторыми неприятностями. У меня есть неопределенное количество массивов объектов типа T. A реализует интерфейс I. Мне нужен массив I в конце, который представляет собой сумму всех значений из всех массивов. Предположим, что никакие массивы не будут содержать одинаковые значения.

Этот Java-код работает.

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()]);

Однако этот код С# не делает:

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();

Итак, очевидно, что мне нужно каким-то образом преобразовать массив T[] в IEnumerable<I>, чтобы добавить его в список, но я не уверен, как лучше всего это сделать? Какие-либо предложения?

РЕДАКТИРОВАТЬ: Разработка в VS 2008, но необходимо скомпилировать для .NET 2.0.


person Adrian Hope-Bailie    schedule 17.06.2009    source источник
comment
Неверный аргумент в обоих случаях. новый список‹I›(массивA) — не работает list.AddRange(новый список‹T›(массивA)) — не работает   -  person Adrian Hope-Bailie    schedule 17.06.2009
comment
Спасибо, на этом я закончил. Ответ от Дэна также работает хорошо. Не знаете, почему прямое приведение AddRange((I[]) arrayOfA) не работает, когда пример Торстена работает нормально?   -  person Adrian Hope-Bailie    schedule 17.06.2009
comment
Я думаю, вы можете сделать это в Java из-за стирания типов (bleh!). За кулисами ArrayList‹T› на самом деле является ArrayList‹?›, а ArrayList‹I› также является ArrayList‹?›.   -  person R. Martinho Fernandes    schedule 17.06.2009
comment
Смотрите мой ответ. Вам нужно ограничить T ссылочными типами, чтобы получить неявное преобразование из T[] в I[].   -  person Rasmus Faber    schedule 17.06.2009
comment
Кстати. Вы можете использовать Linq, ориентируясь на .NET 2.0. Загляните в Linqbridge: code.google.com/p/linqbridge.   -  person Rasmus Faber    schedule 17.06.2009


Ответы (8)


Проблема здесь в том, что C# не поддерживает ковариация (по крайней мере, до C# 4.0, я думаю) в дженериках, поэтому неявные преобразования универсальных типов не будут работать.

Вы можете попробовать это:

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

Для тех, кто сталкивается с этим вопросом и использует .NET 3.5, это немного более компактный способ сделать то же самое с помощью Linq.

List<I> list = new List<I>();
foreach (T[] arrayOfA in arrays)
{
    list.AddRange(arrayOfA.Cast<I>());
}
return list.ToArray();
person Dan Herbert    schedule 17.06.2009
comment
Вот так, очень элегантно. Пришлось дать несколько +1, но в итоге использовал это. Спасибо. Все еще сбит с толку ответом Тростена. Не уверен, почему это не сработает в моем примере, но его код работает нормально? - person Adrian Hope-Bailie; 17.06.2009
comment
Адриан: может быть, твой Т — тип значения? Ковариация типа массива работает только со ссылочными типами. В вашем примере попробуйте выполнить кастинг следующим образом: (IEnum‹I›)(I[])arrayOfA — это работает? - person Eric Lippert; 17.06.2009
comment
Ага! Бинго. T - это тип значения, я думаю, мне следовало указать это заранее. Сегодня узнаю много нового. К сожалению, хотя ваше предложение не помогло, я все еще придерживаюсь ответа выше. - person Adrian Hope-Bailie; 17.06.2009

Отредактировано для 2.0; он может стать:

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

Как насчет (в .NET 3.5):

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

Чтобы показать это в контексте:

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) };
    }
}
person Marc Gravell    schedule 17.06.2009
comment
Из любопытства, какова цель SelectMany? Ты опередил меня на секунды на .Cast, так что +1 от меня :-) - person Dan F; 17.06.2009
comment
SelectMany эффективно объединяет элементы массивов. - person Jon Skeet; 17.06.2009
comment
Awesomecake, я думаю, что добавлю это в список интересных вещей, которые я не знал о LINQ. Та ребята - person Dan F; 17.06.2009
comment
Извините, нужно было указать, что его нужно скомпилировать для 2.0. Но, как говорит Дэн, есть очень классные вещи LINQ. - person Adrian Hope-Bailie; 17.06.2009
comment
Cast‹IFoo›() не нужен. Это было бы необходимо, если бы вы присваивали IEnumerable‹IFoo› вместо IFoo[] (до C# 4.0, когда и там это становится ненужным). - person Daniel Earwicker; 17.06.2009
comment
(Хотя, конечно, это действительно должно было быть необходимо для массивов, поскольку они изменяемы!) - person Daniel Earwicker; 17.06.2009
comment
Требуется для массивов объектов типа значения ;-p - person Marc Gravell; 17.06.2009

Я предполагаю, что вы пытались

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

уже?

EDIT В ответ на ваш комментарий о том, что мое предложение не сработает: я успешно запустил этот код всего минуту назад:

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") };
        }
    }
}

Или я просто пропустил требование?

person Thorsten Dittmar    schedule 17.06.2009
comment
Я не понимаю, почему вышеперечисленное не должно работать. Я отредактировал свой ответ и опубликовал пример кода, который работает. - person Thorsten Dittmar; 17.06.2009
comment
Нет, не могу понять, как это работает, потому что это определенно не работает в моем примере, и все же ваш код компилируется и работает нормально. Насколько я понял, вы никогда не можете привести от T[] к I[], даже если T:I. Извините за сомнения, я должен извиниться! - person Adrian Hope-Bailie; 17.06.2009
comment
Не нужно извиняться! Мне просто любопытно, почему ваш код не работает, а мой работает. Я всегда думал, что возможность приведения от T[] к I[] является частью полиморфизма, если T реализует I? Кроме того, я даже все время делаю обратное, например, привожу от DataRow[] к TypedDataSetRow[]. - person Thorsten Dittmar; 17.06.2009
comment
Преобразования ковариантных массивов работают в том смысле, что они допустимы, но они не работают, так как это создает ситуации, в которых безопасность типов скомпрометирована. Они допустимы в C# и Java; Я бы хотел, чтобы они никогда не были узаконены ни в том, ни в другом. См. мою статью на эту тему: blogs.msdn.com/ericlippert/archive/2007/10/17/ - person Eric Lippert; 17.06.2009
comment
Выше есть предположение, что причина, по которой это не работает в моем случае, заключается в том, что T фактически является структурой (или типом значения). Я так понимаю это правильно? Может быть, вы подтвердите? (Извините, я не упомянул об этом в исходном вопросе) - person Adrian Hope-Bailie; 17.06.2009

Попробуйте добавить общее ограничение

где Т:Я

person Noel Kennedy    schedule 17.06.2009

Я предполагаю, что проблема здесь в том, что дженерики не понимают, что T реализует I. Может сработать явное объявление T : I.

Также вы можете выполнить цикл for и добавлять свои объекты T по одному вместо использования AddRange.

person Brian Reiter    schedule 17.06.2009

Структура типов С# в настоящее время не поддерживает это (обработка Foo<T> как Foo<I>, если X : I), это называется ковариацией на I.

Базовая структура делает это, и c# 4.0 добавляет поддержку это

Поскольку требуются такие явные приведения, ответ Марка будет самым простым.

person ShuggyCoUk    schedule 17.06.2009

В вашем коде С# вы не добавляете arrayOfA в список результатов:

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

return list.ToArray();

Однако, если вы используете .NET 3.5, вы можете сделать это с помощью LINQ:

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

Или с помощью методов LINQ:

return arrays.SelectMany(arrayOfA => arrayOfA.Cast<I>()).ToArray();
person Bojan Resnik    schedule 17.06.2009
comment
Да, хорошо подмечено. Я думаю, что все поняли суть вопроса, тем не менее. Исправлено и +1 для вас за острое зрение :) (Unf no LINQ, поэтому не смог использовать ваше решение.) - person Adrian Hope-Bailie; 17.06.2009
comment
Код, отличный от LINQ, должен вам подойти, тем не менее, как говорит Расмус, массивы ссылочных объектов ковариантны в C#. - person Bojan Resnik; 17.06.2009
comment
Я предполагаю, что код, отличный от LINQ, не работает по той же причине, по которой я не могу преобразовать форму T[] в I[]. На данном этапе кажется, что это связано с тем, что T на самом деле является типом значения. Снова в школу для меня! - person Adrian Hope-Bailie; 17.06.2009

Массивы ссылочных объектов ковариантны в C# (то же верно и для Java).

Из названия я предполагаю, что ваш T является универсальным, а не реальным типом, поэтому вам нужно ограничить его ссылочным типом, чтобы получить неявное преобразование из T[] в I[].

Попробуй это:

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