在C#类型约束中指定“任何子类”而不是“一个特定子类”

时间:2011-10-20 02:18:49

标签: c# polyvariadic

如果我想编写一个方法,该方法采用可变数量的“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 个答案:

答案 0 :(得分:6)

在您的示例中,您实际上告诉编译器doStuff的所有参数在编译时必须是相同的类型,并且此类型必须从Base继承。如果您想允许参数具有不同的类型,那么就不要使用泛型:

void doStuff(params Base[] args)
{}

修改

这同样适用于您的新示例 - 而不是特定SomeReadOnlyCollection,您可以使用IEnumerable,因为它是covariant

void doStuff(params IEnumerable<Base>[] args)
{
    foreach (var list in args)
    {
        foreach (var thing in list)
        {
        }
    }
}

答案 1 :(得分:6)

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 { }  
自.NET 2.0以来,

IEnumerable<T>由BCL集合实现,包括T[]List<T>ReadOnlyCollection<T>HashSet<T>等。

答案 2 :(得分:0)

嗯,你当然可以改变

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

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

然后如果Super是TDerived就可以了。

我可能误解了你,但是使方法采用基类的任何子类的唯一方法是声明方法以引用基类型。

答案 3 :(得分:0)

您可以使用的另一个替代方法是简单地明确指定泛型参数。例如:

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

doStuff<Super>(s0, s1);

您应该能够对SomeReadOnlyCollection的案例应用相同的原则,只要它是covariant。例如,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);