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

发布于 2024-12-10 18:35:56 字数 842 浏览 0 评论 0原文

如果我想编写一个采用可变数量的“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
        }
    }
}

If I would like to write a method that takes a variable number of "TDerived" where TDerived is any subclass of a class "Base", is there any way to do this?

The following code only works with a single specific specified subclass:

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

ie if I have

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

then I cannot do

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

since I get "best overloaded match... has some invalid arguments".

Regardless of how the compiler handles the type constraints and variadic functions, this seems (as far as I can tell) completely type-safe. I know I could cast, but if this is type safe why not allow it?

EDIT:

Perhaps a more convincing example:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

捂风挽笑 2024-12-17 18:35:56

TDerived 需要能够解析为单一类型。在您的示例中,它可以解析的唯一类型是Super,但编译器不会实现这一飞跃。您可以让编译器实现这一飞跃。

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

关于您的更新,请考虑(而不是通用方法)定义一个接受 IEnumerable 的方法,该方法将支持派生类型,因为 IEnumerable code> 是协变的(从 .NET 4 开始)。 IEnumerable 本质上也是只读和只进的,如果您有 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 从 .NET 2.0 开始由 BCL 集合实现,包括 T[]List、< code>ReadOnlyCollectionHashSet 等。

TDerived needs to be able to resolve to a single type. In your example, the only type it could resolve to would be Super, but the compiler is not going to make that leap. You can make that leap for the compiler.

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

Regarding your update, consider (instead of a generic method) defining a method accepting IEnumerable<ISuper>, which will support derived types because IEnumerable<T> is covariant (as of .NET 4). IEnumerable<T> is also inherently readonly and forward-only, perfect if you have a foreach loop. Full working example:

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> is implemented by BCL collections since .NET 2.0, including T[], List<T>, ReadOnlyCollection<T>, HashSet<T>, etc.

一个人的旅程 2024-12-17 18:35:56

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

void doStuff(params Base[] args)
{}

编辑

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

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

In your example, you are actually telling the compiler that all arguments to doStuff must be of the same type at compile time, and that this type has to be inherited from Base. If you want to allow the arguments to be of different types, then just don't use generics:

void doStuff(params Base[] args)
{}

EDIT

The same applies with your new example - instead of a specific SomeReadOnlyCollection you can use IEnumerable, as it is covariant:

void doStuff(params IEnumerable<Base>[] args)
{
    foreach (var list in args)
    {
        foreach (var thing in list)
        {
        }
    }
}
星星的轨迹 2024-12-17 18:35:56

好吧,你当然可以更改

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

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

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

我可能会误解您的意思,但使方法采用基类的任何子类的唯一方法是声明该方法以采用对基类型的引用。

Well you could most certainly change

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

To

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

and then it would work if Super is TDerived.

I may be misunderstanding you, but the only way to make a method take any subclass of a base class is to declare the method to take a reference to the base type.

2024-12-17 18:35:56

您可以使用的另一种替代方法是简单地显式指定通用参数。例如:

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

One other alternative you could use is to simply specify the generic parameter explicitly. For example:

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

doStuff<Super>(s0, s1);

You should be able to apply the same principle on the case with SomeReadOnlyCollection, as long as it is covariant. For example, IEnumerable is such a collection:

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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文