Есть ли способ привести общие списки в списки типов интерфейса / базового класса? - PullRequest
3 голосов
/ 09 октября 2008

Я пытаюсь показать кому-то использование интерфейсов в безумной ситуации, которую они создали. У них есть несколько несвязанных объектов в списках, и им нужно выполнить операцию с двумя строковыми свойствами в каждом объекте. Я отмечаю, что если они определяют свойства как часть интерфейса, они могут использовать объект интерфейса в качестве типа для параметра метода, который воздействует на него; например:

void PrintProperties(IEnumerable<ISpecialProperties> list)
{
    foreach (var item in list)
    {
        Console.WriteLine("{0} {1}", item.Prop1, item.Prop2);
    }
}

Кажется, что это все хорошо, но списки, над которыми нужно работать, не должны (и не должны) объявляться с интерфейсом в качестве параметра типа. Однако не похоже, что вы можете привести к другому параметру типа. Например, это терпит неудачу, и я не могу понять, почему:

using System;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Test> myList = new List<Test>();
            for (int i = 0; i < 5; i++)
            {
                myList.Add(new Test());
            }

            PrintList((IEnumerable<IDoSomething>)myList);
        }

        static void PrintList(IEnumerable<IDoSomething> list)
        {
            foreach (IDoSomething item in list)
            {
                item.DoSomething();
            }
        }
    }

    interface IDoSomething
    {
        void DoSomething();
    }

    public class Test : IDoSomething
    {
        public void DoSomething()
        {
            Console.WriteLine("Test did it!");
        }
    }
}

Я могу использовать для этого член Enumerable.Cast<T>, но я искал метод, который мог бы работать и в .NET 2.0. Кажется, это должно быть возможно; что мне не хватает?

Ответы [ 5 ]

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

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

Что касается исправления для IEnumerable , попробуйте следующее:

public static IEnumerable<TBase> SafeConvert<TBase, TDerived>(IEnumerable<TDerived> source)
    where TDerived : TBase
{
    foreach (TDerived element in source)
    {
        yield return element; // Implicit conversion to TBase
    }
}

РЕДАКТИРОВАТЬ: другой существующий ответ лучше, чем выше, для этого конкретного случая, но я оставлю это здесь, как правило, полезную вещь, если вам действительно нужно «преобразовать» последовательность.

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

Проблема в методе, а не в том, как он называется .....

void PrintProperties<SP>(IEnumerable<SP> list) where SP: ISpecialProperties
{
    foreach (var item in list)
    {
        Console.WriteLine("{0} {1}", item.Prop1, item.Prop2);
    }
}
1 голос
/ 10 октября 2008

То, что вы хотите, называется «ковариация интерфейса» и в настоящее время не поддерживается C #. Вы можете прочитать об этом в блоге Эрика Липперта .

1 голос
/ 09 октября 2008

Вы можете просто использовать foreach в ваших списках. foreach делает встроенный актерский состав. Так что если вы возьмете цикл из функции, вы можете написать что-то вроде

List<Test> myList = new List<Test>();
for (int i = 0; i < 5; i++)
{
   myList.Add(new Test());
}

foreach (IDoSomething item in myList)
{
   item.DoSomething();
}
0 голосов
/ 09 октября 2008

Это не отвечает на ваш вопрос (или, я полагаю, смысл упражнения), но я бы просто использовал отражение в этом случае, прикрепив специальные атрибуты к интересующим свойствам.

...