Неправильно ли приводить перечислитель дочернего класса к перечислителю родительского класса? - PullRequest
3 голосов
/ 23 октября 2008

В моей сборке есть ошибка, которая говорит:

Ошибка 12 Не удается неявно преобразовать тип 'System.Collections.Generic.IEnumerator ' в 'System.Collections.Generic.IEnumerator '. Явное преобразование существует (вы пропустил актерский состав?)

Неужели просто выбросить это?

Это мой код:

public Dictionary<Int32, BaseClass> Map { get; private set; }

public IEnumerator<BaseClass> GetEnumerator()
        {
            return this.Map.Values.GetEnumerator();
        }

public IEnumerator<IParentClass> IEnumerable<IParentClass>.GetEnumerator()
        {
            return this.GetEnumerator(); // ERROR!
        }

У меня вопрос, могу ли я просто изменить эту строку:

return this.GetEnumerator();

до:

return (IEnumerator<IParentClass>)this.GetEnumerator();

(без каких-либо плохих побочных эффектов)?

Принятый ответ:
Я изменил функцию на следующую (после прочтения поста Джона Скита):

IEnumerator<IParentClass> IEnumerable<IParentClass>.GetEnumerator()
        {
            return this.Map.Values.Cast<IParentClass>().GetEnumerator();
        }

Ответы [ 5 ]

5 голосов
/ 23 октября 2008

Нет, вы не можете, потому что дженерики не ковариантны в C # в данный момент. В самой .NET есть некоторая поддержка (для делегатов и интерфейсов), но она пока не используется.

Если вы возвращали IEnumerable<BaseClass> вместо IEnumerator<BaseClass> (и предполагая .NEt 3.5), вы могли бы использовать Enumerable.Cast - но в настоящее время вам нужно написать свой собственный метод расширения, например,

public static IEnumerator<TParent> Upcast<TParent, TChild>
    (this IEnumerator<TChild> source)
    where TChild : TParent
{
    while (source.MoveNext())
    {
        yield return source.Current;
    }
}

В качестве альтернативы в вашем случае вы могли бы использовать Cast ранее:

return this.Map.Values.Cast<BaseClass>().GetEnumerator();
2 голосов
/ 23 октября 2008

Нет, вы не можете, по крайней мере, в C # 3.0 и ниже не поддерживается дисперсия интерфейса. Посмотрите превосходную серию Эрика Липперта об этом, и конкретно этот .

0 голосов
/ 23 октября 2008

По причине , почему это не подходит, картинка вместо Enumerator, List. Оба используют дженерики - компилятор не обрабатывает ни один из них особым образом по отношению к дженерическим аргументам.

void doStuff() {
    List<IParentThing> list = getList();
    list.add(new ChildThing2());
}

List<IParentThing> getList() {
    return new List<ChildThing1>();  //ERROR!
}

Этот первый метод хорош - список IParentThing s должен быть в состоянии получить ChildThing2. Но список ChildThing1 s не может обрабатывать ChildThing2 или любой другой реализатор IParentThing, кроме ChildThing1 - другими словами, если List&lt;ChildThing1> было разрешено разыгрывать как List&lt;IParent>, он бы должен иметь возможность иметь дело со всеми подклассами IParentThing, а не только IParentThing и ChildThing1.

Обратите внимание, что у дженериков Java есть способ сказать, что «я хочу список всего, что унаследовано от этого» в дополнение к «я хочу список всего, что унаследовано», что позволяет сделать более интересным (и, на мой взгляд, элегантно) решения некоторых проблем.

0 голосов
/ 23 октября 2008

Нет, это не безопасно, см. Ниже:

с использованием System.Collections.Generic; класс Foo {} Класс Bar: Foo {}

static class Program
{
    static IEnumerator<Foo> GetBase() {
        yield return new Foo();
        yield return new Bar();
    }
    static IEnumerator<Bar> GetDerived()
    {
        return (IEnumerator<Bar>)GetBase();
    }
    static void Main()
    {
        var obj = GetDerived(); // EXCEPTION
    }
}

Однако, вы должны быть в состоянии использовать блок итератора, чтобы выполнить приведение за вас?

static IEnumerator<Bar> GetDerived()
{
    using (IEnumerator<Foo> e = GetBase())
    {
        while (e.MoveNext())
        {
            // or use "as" and only return valid data
            yield return (Bar)e.Current;
        }
    }
}
0 голосов
/ 23 октября 2008

IEnumerator<BaseClass> и IEnumerator<ParentClass> не связаны, хотя их общие параметры. Вместо этого я бы использовал метод расширения LINQ Select следующим образом:

return this.Select(x => (IParentClass)x).GetEnumerator();

или метод расширения Cast:

return this.Cast<IParentClass>().GetEnumerator();
...