开发者

How to use generics with overloading?

开发者 https://www.devze.com 2022-12-15 14:34 出处:网络
Edit: C# 3.0, net 3.5. I am C++ programmer, so maybe I miss some simple solution in C#. Simplified example:

Edit: C# 3.0, net 3.5.

I am C++ programmer, so maybe I miss some simple solution in C#.

Simplified example: I have several classes inherited from class MyBase (with method Inc). The inherited classes may override Inc. I have several overloaded "functions" (static methods) for the inherited classes:

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.

I have generics function that modify its argument and call the Print function:

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.

Now, in C++ it would work just like that. However in C# method Inc is unknown. So I specify that T is of type 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.

As a workaround I used ugly (!) solution -- PrintProxy wh开发者_Python百科ere I put several

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

How to mix those two concepts -- overloading and generics? Nice, C# way?

Thank you in advance for help.


One option in C# 4 is to use dynamic typing:

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.

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

Obviously this dynamic approach has downsides:

  • It requires .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)

Basically this is just a difference between .NET generics and C++ templating - they're very different creatures, even if they tackle many similar problems.

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? 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? 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. 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 (e.g. 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.


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:

 interface IMyBase 
 {
     void Inc();
 }

Option A

[Edit] Since Print does not belong to IMyBase, you could separate the printing logic completely and do something like this:

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

where IPrintLogic is defined as:

 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.

Option B - Creating an intermediate object (like the Adapter pattern)

Depending on what your Transform method actually does, this may be an option also:

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

and then print the IPrepared object, which is "prepared" for printing in a way:

 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? If none is specified it's private by default which would preclude derived classes from accessing it - mark it protected at least.

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

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消