【问题标题】:How do I use a generic class without specifying its type parameters in C#?如何使用泛型类而不在 C# 中指定其类型参数?
【发布时间】:2022-09-30 21:48:26
【问题描述】:

我知道之前已经提出了多个类似的问题,但是我仍然相信这个特定的问题还没有解决。

有一个类Super 和一个泛型类Foo<T> : Super,它们都来自第三方库。我需要扩展Foo<T> 的功能,所以我有这个通用类:

public class Bar<T, U> : Foo<T> {
  public void DoSomething() { ... }

  // Other things that use T and U
}

我现在想要一个“酒吧”的集合(列表),并且能够在所有这些上调用 DoSomething。所以从逻辑上讲,当调用 void 方法时,我不关心 Bar 的类型参数,因为它在那种情况下是无关紧要的。

此外,第三方库还公开了以下方法:

public LibraryMethod(Super super) { ... }

而且我需要能够用我的任何“酒吧”来调用该方法。

非解决方案 1:接口

我不能使用接口的原因是,因为第三方库在它暴露的一些方法中期望Super,并且接口可以被任何类实现,所以我不能使用我的Bar\'s as Super \'s 即使每个 Bar(无论它的类型参数是什么)都必须是 Super。该接口基本上破坏了这里的继承链,我不能再将我的“Bar”传递给期望 Super 的方法。

非解决方案 2:非泛型基类

显然我不能让Bar&lt;T, U&gt; 继承自非泛型基类,原因与上述相同。它也会破坏继承链。

虚构的理想情况

在理想情况下,我可以这样做:

List<Bar<>> bars = new List<Bar<>>();

bars.Add(new Bar<Type1, Type2>());
bars.Add(new Bar<Type3, Type4>());

foreach (Bar<> bar in bars) {
  bar.DoSomething();
}

LibraryMethod(bars[0]);
  • 为什么不能像public interface IBar { void DoSomething(); }这样声明一个接口,然后像foreach (IBar bar in listy) { bar.DoSomething(); }一样使用它
  • 好吧,你总是可以将它转换为不同的类型,所以这仍然不能解释为什么接口不起作用。也许您可以发布更多代码来更好地解释您的问题。
  • LibraryMethod() 在哪里声明?这很重要,因为您可能能够利用编译器为方法提供的自动类型参数解析。
  • 我不明白问题是什么。所有Bar&lt;T,U&gt; 派生自Foo&lt;T&gt;,而Foo&lt;T&gt; 派生自Super。所以你用所有的酒吧打电话给LibraryMethod(Super super)。请解释问题是什么。
  • @JohnAlexiou OP 想要具有两种类型限制 (MyList&lt;T, R1,R2&gt;where T:R1, T:R2) 的列表,支持迭代“列表”为 IEnumerable&lt;R1&gt;IEnumerable&lt;R2&gt;... 但由于 C# 不是 Java,他们甚至有问题将这些项目添加到初始列表中,但目前专注于从两种类型调用方法......(这就是我看到问题的方式)。

标签: c# generics generic-type-argument


【解决方案1】:

简而言之 - 您不能,C# 不支持实例化开放泛型类型的实例,并且您不能将列表限制为同时为 SuperIDoSomething 接口。而且你不能从一些非泛型类型(继承Super并实现IDoSomething)和Foo&lt;&gt;另外继承Bar

在这种特殊情况下,您可以将AsSuper 添加到您的界面:

public interface IDoSomething
{
   void DoSomething();
   Super AsSuper();
}

Bar 中应该很容易实现:

public class Bar<T, U> : Foo<T>, IDoSomething {
  public void DoSomething() { ... }
  public Super AsSuper() => this;
  // Other things that use T and U
}

这将允许您安全地键入调用LibraryMethod

LibraryMethod(bars[0].AsSuper());

【讨论】:

  • 这看起来很有希望,因为它基本上通过将“is-a”变成“can-be-made-into-a”来解决继承链问题。非常聪明!目前正在检查这是否真的解决了我的问题......
  • @TheHelpfulHelper 请注意,这种方法有一个警告——如果LibraryMethod 改变了参数并且AsSuper 实现会返回其他东西(例如新实例——new Super(...)),则系统的行为可能是错误的。
  • 啊,当然有一个问题。好吧,我想如果程序员不注意他们在做什么,任何代码都可能出错。但这很好指出。我仍然认为这是迄今为止解决问题的最佳方法。
  • 这确实解决了我的问题。但是,我认为标题中所述的原始问题并没有由此解决。但相反,这是一个解决原始问题不可能性的工作。所以我应该更新问题标题并将其标记为答案?
  • @TheHelpfulHelper 你现在真的不能改变问题,因为有解决其他部分的答案......保持问题不变可能是可以的。我不会添加更多,因为它已经足够长了:)
【解决方案2】:

您可能会在编译时丢失类型信息并在其他地方进行跟踪。也许你有程序知识(你,程序员)并且知道类型信息。否则,您必须创建一些数据结构并携带此信息。无论如何,您可以在这里使用接口。

考虑您目前的情况(不起作用):

public class Super { public int SuperData { get; set; } }
public class Foo<T> : Super { public T FooData { get; set; } }
public class Bar<T, U> : Foo<T>
{
    public U BarData { get; set; }
    public void DoSomething()
    {
        Console.WriteLine("i do something");
    }
}

public static void LibraryMethod(Super super) { Console.WriteLine("i'm super"); }
public void Main()
{
    // generic "object" collection
    var listy = new List<object>();
    listy.Add(new Bar<int, double>());
    listy.Add(new Bar<DateTime, bool>());
    
    foreach (var x in listy)
    {
        x.DoSomething(); /// 'object' does not contain a definition for 'DoSomething'

        // and then call your library
        LibraryMethod((Super)x);
    }
}

相反,添加一个接口并在您的类型中实现:

public interface IDoSomething
{
    void DoSomething();
}

public class Bar<T, U> : Foo<T>, IDoSomething
{
    public U BarData { get; set; }
    public void DoSomething()
    {
        Console.WriteLine("i do something");
    }
}

现在您可以使用 with cast 来更正接口类型:

public void Main()
{
    var listy = new List<object>();
    listy.Add(new Bar<int, double>());
    listy.Add(new Bar<DateTime, bool>());
    
    foreach (var x in listy)
    {
        var bar = x as IDoSomething;
        // check null reference
        bar.DoSomething();

        // and then call your library
        LibraryMethod((Super)x);
    }
}

输出:

> Main()
i do something
i'm super
i do something
i'm super

【讨论】:

  • 这只适用,只要您小心不要在不从 Super 继承的类上实现 IDoSomething。否则强制转换将在运行时抛出异常。因此,这是类型不安全的,并且有点错过了强类型语言(例如 C#)的要点
  • 呃,什么,你问如何实现一个不使用基类中的任何东西的方法?但这不是接口的工作方式,如果您的类型实现了接口,那么它将强制转换。
  • 你正在做一个(超级)x演员。 x 是一个 IDoSomething。如果我创建一个新类“Buz”并使其实现 IDoSomething 并创建该类的实例“buz”,那么我不能做(Super)buz,因为 Buz 不是从 Super 继承的。那将是非法演员。
  • 啊,对,当然,所以检查空引用与上面两行一样。这个答案只是一个指南。如果你需要改变指导方针来实现某个目标,你将不得不找出到达那里的安全方法。
【解决方案3】:

你不能做你描述的事情。 用 List 或 KeyValuePair 替换您的 bar 类以查看结果:

var myList = new List<List<>>(); 
var myListOfKv = new List<KeyValuePair<>>(); 

您正在尝试分配一些内存,编译器需要知道一些约束。 您也许可以尝试对以下类型进行一些限制: 类 Bar<x,y> :Foo 其中 x 是 ISoda

您可能还会想到另一种方法来扩展功能,方法是在您的孩子中添加特定的方法来处理对象:

class Bar<x>: Foo<x>
{
    public void Inject(Iy iy);
    public void UseInjected();
}

第三方库仍将接受 Bar 实例并忽略新功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-19
    • 2011-11-26
    • 2023-01-19
    • 1970-01-01
    • 2022-07-05
    • 1970-01-01
    相关资源
    最近更新 更多