简体   繁体   English

如何在泛型中使用泛型?

[英]How to use generics with overloading?

Edit: C# 3.0, net 3.5. 编辑: C#3.0,净3.5。

I am C++ programmer, so maybe I miss some simple solution in C#. 我是C ++程序员,所以也许我想念一些C#中的简单解决方案。

Simplified example: I have several classes inherited from class MyBase (with method Inc). 简化的示例:我有几个类继承自MyBase类(使用Inc方法)。 The inherited classes may override Inc. I have several overloaded "functions" (static methods) for the inherited classes: 继承的类可能会覆盖Inc。对于继承的类,我有几个重载的“函数”(静态方法):

void Print(MyInherited1 i1) ....
void Print(MyInherited2 i2) ....

and so on. 等等。 Edit: those methods are "external" to the MyBase and MyInherited, they are not part of any of those classes. 编辑:这些方法是MyBase和MyInherited的“外部”,它们不属于任何这些类。

I have generics function that modify its argument and call the Print function: 我有一个泛型函数,可以修改其参数并调用Print函数:

void Transform<T>(T t)
{
    t.Inc();
    Print(t);
}

Edit: this is simplified example, in real code I cannot convert the Transform method to non-generic one using just polymorphism. 编辑:这是一个简化的示例,在真实代码中,我无法仅使用多态将Transform方法转换为非通用方法。

Now, in C++ it would work just like that. 现在,在C ++中,它将像那样工作。 However in C# method Inc is unknown. 但是,在C#方法中,Inc是未知的。 So I specify that T is of type MyBase. 因此,我指定T为MyBase类型。

void Transform<T>(T t) where T : MyBase
{
    t.Inc();
    Print(t);
}

but I still have the problem with Print -- there is no such method for the base class. 但是我仍然遇到Print的问题-基类没有这种方法。

As a workaround I used ugly (!) solution -- PrintProxy where I put several 作为一种解决方法,我使用了丑陋的(!)解决方案-在其中放置了几个的PrintProxy

if (t is Inherited1)
  Print(t as Inherited1);
else if ...

How to mix those two concepts -- overloading and generics? 如何混合使用这两个概念-重载和泛型? Nice, C# way? 好的,C#方式吗?

Thank you in advance for help. 预先感谢您的帮助。

One option in C# 4 is to use dynamic typing: C#4中的一种选择是使用动态类型:

void Transform<T>(T t)
{
    t.Inc();
    dynamic d = t;
    Print(d);
}

That will perform the overload resolution for you at execution time - which is basically the best you can do unless you can provide a genuinely generic Print method, as there will only be one version of Transform generated. 这将在执行时为您执行重载解析-这基本上是您可以做的最好的事情,除非您可以提供真正通用的Print方法,因为只会生成一个版本的Transform

Note that there's no real need to be generic at this point - you could replace it with: 请注意,此时实际上并不需要通用-您可以将其替换为:

void Transform(MyBase t)
{
    ...
}

I typically find that constraints based on interfaces are more useful than those based on classes, unless I'm also doing something else generic (such as creating a List<T> which should be of the right actual type). 通常,我发现基于接口的约束比基于类的约束更有用,除非我也做其他通用的事情(例如创建应该是正确的实际类型的List<T> )。

Obviously this dynamic approach has downsides: 显然,这种动态方法具有以下缺点:

  • It requires .NET 4.0 它需要.NET 4.0
  • It's slower than a compile-time binding (although it's unlikely to be significant unless you're calling it a heck of a lot) 它比编译时绑定慢(尽管除非您称其为千篇一律,否则它并不重要)
  • There's no compile-time validation (you can add an overload which takes object as a sort of fallback to provide custom error handling, but you still have to wait until execution time) 没有编译时验证(您可以添加重载,将object作为一种后备,以提供自定义错误处理,但是您仍然必须等到执行时间)

Basically this is just a difference between .NET generics and C++ templating - they're very different creatures, even if they tackle many similar problems. 基本上,这只是.NET泛型和C ++模板之间的区别-它们是非常不同的生物,即使它们解决了许多类似的问题。

Rather than having static Print methods, is there any reason you can't write an abstract Print method in MyBase , and override it in each class? 除了使用静态Print方法外,还有什么理由不能在MyBase编写抽象Print方法并在每个类中覆盖它? That feels like a more OO solution anyway, to be honest - although obviously it doesn't make sense if the printing is actually somewhat logically distant from the class itself. 坦白地说,这感觉上还是一个面向对象的解决方案-尽管显然打印实际上在逻辑上与类本身没有任何距离,这显然没有任何意义。 Even if you don't want the actual Print method in the original type hierarchy, might you be able to expose enough functionality to let you write a virtual Print method? 即使您不希望在原始类型层次结构中使用实际的 Print方法,您是否也可以公开足够的功能来编写虚拟Print方法? I assume all these methods should come up with some similar kind of result, after all. 毕竟,我认为所有这些方法都应该得出类似的结果。

EDIT: A couple of alternative ideas... 编辑:几个替代的想法...

Passing in the printer 送入打印机

You can pass in a delegate to do the printing. 您可以传递一个委托进行打印。 If you're calling this from a non-generic context which knows the actual type, you can take advantage of method group conversions to make this simple. 如果从已知实际类型的非泛型上下文中调用此方法,则可以利用方法组转换来简化此过程。 Here's a short but complete example: 这是一个简短但完整的示例:

using System;

class Test
{
    static void SampleMethod<T>(T item, Action<T> printer)
    {
        // You'd do all your normal stuff here
        printer(item);
    }

    static void Print(string x)
    {
        Console.WriteLine("Here's a string: {0}", x);
    }

    static void Print(int x)
    {
        Console.WriteLine("Here's an integer: {0}", x);
    }

    static void Main()
    {
        SampleMethod(5, Print);
        SampleMethod("hello", Print);
    }
}

Use a type/delegate dictionary 使用类型/委托字典

Another option is to have a Dictionary<Type, Delegate> containing the printing methods. 另一个选择是让Dictionary<Type, Delegate>包含打印方法。 This could either be inlined (if the printing methods are simple) or something like this: 可以内联(如果打印方法很简单)或类似以下内容:

static readonly Dictionary<Type, Delegate> Printers = 
    new Dictionary<Type, Delegate>
{
    { typeof(MyClass1), (Action<MyClass1>) Print },
    { typeof(MyClass2), (Action<MyClass2>) Print },
    { typeof(MyClass3), (Action<MyClass3>) Print },
};

Then in your method: 然后在您的方法中:

Delegate printer;
if (Printers.TryGetValue(typeof(T), out printer))
{
    ((Action<T>) printer)(t);
}
else
{
    // Error handling
}

This is another execution time solution though, and you'd need a bit more work if you wanted it to handle further derivation (eg walking up through the base classes if it can't find the relevant printer). 但是,这是另一个执行时间解决方案,如果您希望它处理进一步的派生(例如,如果找不到相关的打印机,则遍历基类),则需要做更多的工作。

That's by design. 那是设计使然。 The compiler has to know at compile time what method (overload) to bind to for the unbound (!) generic class, and in your case, this would only be known after the generic type has been set. 对于未绑定(!)泛型类,编译器必须在编译时知道要绑定的方法(重载),并且在您的情况下,只有在设置了泛型类型之后,才能知道这一点。

In contrast to the C++ templates, generics are working in a different way even though they share a similar syntax. 与C ++模板相比,泛型即使共享相似的语法,也以不同的方式工作。

My first suggestion would be to use an interface instead of generics. 我的第一个建议是使用接口而不是泛型。 I see no point in using generics in this example. 在此示例中,使用泛型没有任何意义。

 void Transform(IMyBase t)
 {
    t.Inc();
 }

where IMyBase is: IMyBase在哪里:

 interface IMyBase 
 {
     void Inc();
 }

Option A 选项A

[Edit] Since Print does not belong to IMyBase , you could separate the printing logic completely and do something like this: [编辑]由于Print 属于IMyBase ,因此您可以将打印逻辑完全分开并执行以下操作:

 void Transform(IMyBase t, IPrintLogic printLogic)
 {
    t.Inc();
    printLogic.Print(t);
 }

where IPrintLogic is defined as: IPrintLogic定义为:

 interface IPrintLogic 
 {
     void Print(IMyBase t);
 }

now you have the freedom to instantiate any print logic you want: 现在,您可以自由实例化所需的任何打印逻辑:

 MyInherited1 obj = new MyInherited1();
 MyPrintLogic printer = new MyPrintLogic();
 Transform(obj, printer);

or, use a factory of some sort: 或者,使用某种工厂:

 Transform(obj, PrintLogicFactory.Create(obj));

Code inside your factory could then be similar to a bunch of if/then/else blocks, like you have right now. 这样,工厂内部的代码可能类似于一堆if / then / else块,就像您现在拥有的那样。

Option B - Creating an intermediate object (like the Adapter pattern ) 选项B-创建一个中间对象(如Adapter模式

Depending on what your Transform method actually does, this may be an option also: 根据您的Transform方法的实际作用,也可以选择以下方法:

 IPrepared Transform(IMyBase t)
 {
    t.Inc();
    return this.PrepareForPrinting(t);
 }

and then print the IPrepared object, which is "prepared" for printing in a way: 然后打印IPrepared对象,该对象已“准备好”用于打印:

 interface IPrintLogic 
 {
    void Print(IPrepared t);
 }

In that case, you would use it like: 在这种情况下,您可以像这样使用它:

 MyInherited1 obj = new MyInherited1();
 IPrepared prepared = Transform(obj);

 MyPrintLogic printer = new MyPrintLogic();
 printer.Print(prepared);

why not using an interface? 为什么不使用界面?

void Transform<T>(T t) where T : MyBase, IMyPrintableClass
{
    t.Inc();
    t.Print();
}

What is the access modifier on the .Print(..) method in your MyBase class? MyBase类中.Print(..)方法上的访问修饰符是什么? If none is specified it's private by default which would preclude derived classes from accessing it - mark it protected at least. 如果未指定任何内容,则默认情况下它是private ,这将阻止派生类访问它-至少将其标记为protected

protected void Print(MyBase obj) {
    //do stuff
}

If for some reason Print(..) isn't in the inheritance hierarchy and you're trying to use it in a composited way try public (although your description indicates this isn't the case). 如果出于某种原因Print(..)不在继承层次结构中,并且您尝试以复合方式使用它,请尝试使用public (尽管您的描述表明并非如此)。

public void Print(MyBase obj) { //...

Public is a good way to debug it because your code is most apt to "see" it whether from the same assembly or a different one. Public是调试它的好方法,因为无论是从同一程序集还是从另一个程序集中,您的代码最容易“看到”它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM