Указание «любого подкласса» в ограничении типа C # вместо «одного конкретного подкласса» - PullRequest
5 голосов
/ 20 октября 2011

Если я хотел бы написать метод, который принимает переменное число «TDerived», где TDerived равен любой подкласс класса «Base», есть ли способ сделать это?

Следующий код работает только с одним конкретным указанным подклассом:

void doStuff<TDerived>(params TDerived[] args) where TDerived : Base
{
    //stuff
}

, т.е. если у меня есть

class Super { }
class Sub0 : Super { }
class Sub1 : Super { }

, тогда я не могу сделать

Sub0 s0 = new Sub0();
Sub1 s1 = new Sub1();
doStuff(s0, s1);

, так какЯ получаю «наилучшее перегруженное совпадение ... имеет несколько недопустимых аргументов».

Независимо от того, как компилятор обрабатывает ограничения типов и функции с переменным числом, это кажется (насколько я могу судить) полностью безопасным для типов.Я знаю, что могу разыграть, но если это безопасно, почему бы не разрешить это?

РЕДАКТИРОВАТЬ:

Возможно, более убедительный пример:

void doStuff<TDerived>(params SomeReadOnlyCollection<TDerived>[] args) where TDerived : Base
{
    foreach(var list in args)
    {
        foreach(TDerived thing in list)
        {
            //stuff
        }
    }
}

Ответы [ 4 ]

6 голосов
/ 20 октября 2011

В вашем примере вы на самом деле говорите компилятору, что все аргументы doStuff должны быть одного типа во время компиляции, и что этот тип должен быть унаследован от Base. Если вы хотите, чтобы аргументы были разных типов, просто не используйте обобщенные значения:

void doStuff(params Base[] args)
{}

EDIT

То же относится и к вашему новому примеру - вместо конкретного SomeReadOnlyCollection вы можете использовать IEnumerable, так как он ковариантный :

void doStuff(params IEnumerable<Base>[] args)
{
    foreach (var list in args)
    {
        foreach (var thing in list)
        {
        }
    }
}
6 голосов
/ 20 октября 2011

TDerived должен быть в состоянии разрешить к одному типу. В вашем примере, единственный тип, который может разрешить, будет Super, но компилятор не собирается совершать этот скачок. Вы можете сделать этот скачок для компилятора.

doStuff(new Super[] { s0, s1 });
doStuff<Super>(s0, s1);

Что касается вашего обновления , рассмотрите (вместо универсального метода) определение метода, принимающего IEnumerable<ISuper>, который будет поддерживать производные типы, поскольку IEnumerable<T> является ковариантным (начиная с .NET 4). IEnumerable<T> также изначально доступен только для чтения и только для пересылки, идеально, если у вас есть цикл foreach. Полный рабочий пример:

class Program
{
    static void Main()
    {
        var sub0s = new Sub0[] { new Sub0() };
        var sub1s = new List<Sub1> { new Sub1() };
        doStuff(sub0s, sub1s);
    }

    static void doStuff(params IEnumerable<ISuper>[] args)
    {
        foreach (var sequence in args)
        {
            foreach (var obj in sequence)
            {
                Console.WriteLine(obj.GetType());
                // you have the ability to invoke any method or access 
                // any property defined on ISuper
            }
        }
    } 
}

interface ISuper { }
class Super : ISuper { }
class Sub0 : Super { }
class Sub1 : Super { }  

IEnumerable<T> реализуется коллекциями BCL начиная с .NET 2.0, включая T[], List<T>, ReadOnlyCollection<T>, HashSet<T> и т. Д.

0 голосов
/ 20 октября 2011

Еще одна альтернатива, которую вы можете использовать, - просто явно указать универсальный параметр. Например:

var s0 = new Sub0();
var s1 = new Sub1();

doStuff<Super>(s0, s1);

Вы должны быть в состоянии применить тот же принцип к случаю с SomeReadOnlyCollection, если он ковариантен . Например, IEnumerable - это такая коллекция:

static void doStuff2<TDerived>(params IEnumerable<TDerived>[] args) where TDerived : Super {
    // ...
}

// ...

var l0 = new List<Sub0>();
var l1 = new List<Sub1>();

doStuff2<Super>(l0, l1);
0 голосов
/ 20 октября 2011

Ну, вы наверняка можете изменить

Sub0 s0 = new Sub0();
Sub1 s1 = new Sub1();

Для

Super s0 = new Sub0();
Super s1 = new Sub1();

и тогда будет работать, если Super будет TDerived.

Возможно, я вас неправильно понимаю, но единственный способ заставить метод взять любой подкласс базового класса - это объявить метод, чтобы получить ссылку на базовый тип.

...