views:

981

answers:

5

Coming from a C++ background, I've run into a snag with overloading based on a specific instance of a generic type. The following doesn't work since only once instance of the code for the Foo<T> class is ever generated, so inside the Method, the type of this is simply Foo<T>, not Foo<A> or Foo<B> as I'd hoped. In C++ I'm used to templates being instantiated as unique types.

using System.Collections.Generic;
class A
{
    // Concrete class
}

class B
{
    // Concrete class
}

class Bar
{
    public void OverloadedMethod(Foo<A> a) {} // do some A related stuff
    public void OverloadedMethod(Foo<B> b) {} // do some B related stuff
    public void OverloadedMethod(OtherFoo of) {} // do some other stuff

     public void VisitFoo(FooBase fb) { fb.Method(this); }
}

abstract class FooBase
{
    public abstract void Method(Bar b);
}

class Foo<T> : FooBase
{
    // Class that deals with As and Bs in an identical fashion.
    public override void Method(Bar b)
    {
        // Doesn't compile here
        b.OverloadedMethod(this);
    }
}

class OtherFoo : FooBase
{
    public override void Method(Bar b)
    {
        b.OverloadedMethod(this);
    }
}

class Program
{
    static void Main(string[] args)
    {
     List<FooBase> ListOfFoos = new List<FooBase>();
     ListOfFoos.Add(new OtherFoo());
     ListOfFoos.Add(new Foo<A>());
     ListOfFoos.Add(new Foo<B>());

     Bar b = new Bar();
     foreach (FooBase fb in ListOfFoos)
      b.VisitFoo(fb);
     // Hopefully call each of the Bar::Overloaded methods
    }
}

Is there a way to get something like this to work in C#? I'd rather not have to duplicate the code in Foo as separate classes for every type I want to use it for.

Edit: Hopefully this is a little clearer.

A: 

Edit: I'm not sure that you can complete this as you're attempting. I've tried all sorts of tricks to attempt to get this to work and can't get it to compile. The best I can do is to pull the method call outside of my Generic class. If your method call is outside, then you can specifically define what T is in the generic. However, inside the method, at compile time, the compiler doesn't know what T will be so it doesn't know which overloaded method to call. The only way I can see around this is to use a switch to determine the type of T and manually specify the overload to call.

The best I can do is this, which isn't quite what you're after, but it could be used to a similar effect:

class Stuff<T>
{
    public T value { get; set; }
}

class Program
{
    static void DummyFunc(Stuff<int> inst)
    {
        Console.WriteLine("Stuff<int>: {0}", inst.value.ToString());
    }

    static void DummyFunc(Stuff<string> inst)
    {
        Console.WriteLine("Stuff<string>: {0}", inst.value);
    }
    static void DummyFunc(int value)
    {
        Console.WriteLine("int: {0}", value.ToString());
    }
    static void DummyFunc(string value)
    {
        Console.WriteLine("string: {0}", value);
    }
    static void Main(string[] args)
    {
        var a = new Stuff<string>();
        a.value = "HelloWorld";

        var b = new Stuff<int>();
        b.value = 1;

        var c = "HelloWorld";
        var d = 1;

        DummyFunc(a);
        DummyFunc(b);
        DummyFunc(c);
        DummyFunc(d);
    }
}

and got output:

Stuff<string>: HelloWorld
Stuff<int>: 1
string: HelloWorld
int: 1

I've got four overloaded functions referencing two referencing generic classes (one for int and one for string) and two referencing regular types (one for int and one for string) and it all works okay... is this what you're after?

Edit: The problem doesn't seem to be with the calling of the overloaded methods, it has to do with your foreach which is trying to convert all items in the list to the same type as the first in order to reference the overloaded method. The first item that doesn't conform to that exact definition will cause your compile to fail.

BenAlabaster
That is just overloading - not overloading *within* generics.
Marc Gravell
oh, I get it now, I thought we were trying to overload using generics, not overload from inside a generic itself... it seems I missed the point entirely :P
BenAlabaster
+2  A: 

I now have a genuinely complete piece of code which demonstrates the problem. Note to OP: please try compiling your code before posting it. There were a bunch of things I had to do to get this far. It's good to make it as easy as possible for other people to help you. I've also removed a bunch of extraneous bits. OtherFoo isn't really relevant here, nor is FooBase.

class A {}
class B {}

class Bar
{
    public static void OverloadedMethod(Foo<A> a) { }
    public static void OverloadedMethod(Foo<B> b) { }
}

class Foo<T>
{
    // Class that deals with As and Bs in an identical fashion.
    public void Method()
    {
        // Doesn't compile here
        Bar.OverloadedMethod(this);
    }
}

Yes, this doesn't compile. What did you expect it to do, exactly? Bear in mind that the overload resolution is performed at compile time, not execution time. As fallen888 says, you could cast and call the appropriate overloaded method - but which of the two overloads would you expect the compiler to pick otherwise? What do you want it to do with Foo<string> instead of Foo<A> or Foo<B>?

This all goes to demonstrate that .NET generics are indeed significantly different from C++ templates, of course...

Jon Skeet
The extra classes aren't relative to the compiler error - they are relative to the design I was hoping to convey. I had originally hoped it would have worked, but the compile error made me do some more research as to how generics work.
Eclipse
I was hoping for some sort of workaround to get context where the type of this was Foo<A>, not just an affirmation that I did indeed have a compile error.
Eclipse
But the root is understanding *why* there's a compiler error. Once you understand that (which is pretty much in terms of the differences between generics and C++ templates, IMO) you may well want to change your design anyway.
Jon Skeet
+1  A: 

I haven't tried it but it seems you should be able to achieve what you want by making A & B visitable (e.g. with the acyclic visitor pattern).

Andreas Huber
A: 

I was hoping to find an easier way to do this but for now I'm going with this:

Replace Foo<T> class with these classes:

abstract class Foo<T> : FooBase
{
    // Class that deals with As and Bs in an identical fashion.
}

class Foo_A : Foo<A>
{
    public override void Method(Bar b)
    {
     b.OverloadedMethod(this);
    }
}

class Foo_B : Foo<B>
{
    public override void Method(Bar b)
    {
     // Doesn't compile here
     b.OverloadedMethod(this);
    }
}

And change the instantiations to

List<FooBase> ListOfFoos = new List<FooBase>();
ListOfFoos.Add(new OtherFoo());
ListOfFoos.Add(new Foo_A());
ListOfFoos.Add(new Foo_B());

This at least doesn't require dublicating the code in Foo<T>, and just requires me to forward the constructors.

Eclipse
+1  A: 

This works for the static case. Dealing with instance functions would be a bit more complicated. This post from Jon Skeet might provide a reasonable way to deal with instance methods.

class Program
{
    static void Main(string[] args)
    {
        var testA = new Foo<A>();
        testA.Method();
        var testB = new Foo<B>();
        testB.Method();
        Console.ReadLine();
        var testString = new Foo<string>(); //Fails
        testString.Method(); 
        Console.ReadLine();
    }
}

class A { }
class B { }
class Bar
{
    public static void OverloadedMethod(Foo<A> a)
    {
        Console.WriteLine("A");
    }
    public static void OverloadedMethod(Foo<B> b)
    {
        Console.WriteLine("B");
    }
}
class Foo<T>
{
    static Foo()
    {
        overloaded = (Action<Foo<T>>)Delegate.CreateDelegate(typeof(Action<Foo<T>>), typeof(Bar).GetMethod("OverloadedMethod", new Type[] { typeof(Foo<T>) }));
    }

    public void Method()
    {
        overloaded(this);
    }

    private static readonly Action<Foo<T>> overloaded;
}
Arthur Berkowicz