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.
精彩评论