views:

566

answers:

3

I wonder if it is in any way possible to specialize generic interface methods somehow in C#? I have found similar questions, but nothing exactly like this. Now I suspect that the answer is "No, you can't" but I would like to have it confirmed.

What I have is something like the following.

public interface IStorage
{
    void Store<T>(T data);
}

public class Storage : IStorage
{
    public void Store<T>(T data)
    {
        Console.WriteLine("Generic");
    }

    public void Store(int data)
    {
        Console.WriteLine("Specific");
    }
}

class Program
{
    static void Main(string[] args)
    {
        IStorage i = new Storage();
        i.Store("somestring"); // Prints Generic
        i.Store(1); // Prints Generic
        Storage s = (Storage)i;
        s.Store("somestring"); // Prints Generic
        s.Store(1); // Prints Specific
    }
}

Is there any way to make it use the specialized version of Store when called through the interface? And if not, does anyone know the exact reason why C# treats Generic arguments this way?

Edit: The issue could be worked around if it wasn't so that C# cannot resolve template arguments in more than one step.

void Foo<T>(T t)
{
    SubFoo(t);
}

void SubFoo<T>(T t)
{
    Console.WriteLine("Generic");
}

void SubFoo(int t)
{
    Console.WriteLine("Specific");
}

A call to Foo(1) here will print "Generic" as well, shouldn't the compiler be able to resolve this? Or does the JIT prevent this?

A: 

You could do something like this:

public interface IStorage<T>
{
    void Store(object data);
    void Store<T>(T data);
}

public class Storage : IStorage<int>
{
    public void Store(object data)
    {
        Console.WriteLine("Generic");
    }

    public void Store(int data)
    {
        Console.WriteLine("Specific");
    }
}

You've typed i as IStorage and that interface doesn't define the overloaded Store method.

Jamie Ide
+8  A: 

Overload resolution is performed at compile-time, not at run-time based on the actual type of the passed value.

IStorage i = new Storage();
i.Store("somestring"); // Prints Generic
i.Store(1); // Prints Generic

This will always call the "generic" method, because there is only one overload of Store in IStorage and the compiler doesn't know that i actually contains a Storage object. How can the compiler know about the other overload in Storage?

Storage s = (Storage)i;
s.Store("somestring"); // Prints Generic
s.Store(1); // Prints Specific

Here, the compiler knows that s contains a Storage object (or one deriving from Storage), because s is declared that way. So it sees two overloads. It chooses the specific overload for int values, because overload resolution rules say to prefer specific overloads over generic overloads.


It's technically possible to determine typeof(T) in the generic method at run-time and forward the method call to a specific method. But if you think about it, this doesn't make a lot of sense. A generic method means that the same implementation works for arguments of different, unrelated types. If you want different implementations for different types, you shouldn't use generics for this.


void Foo<T>(T t)
{
    SubFoo(t);
}

void SubFoo<T>(T t);
void SubFoo(int t);

Generics work quite a bit different from C++ templates. The C# compiler compiles Foo only once -- to a generic method. Remember: generic means same implementation for different types. The C# compiler does not know at compile-time if T is going to be an int or a string or any other type. So the only possible implementation of Foo that works for any T is to call SubFoo<T>. If one of the SubFoo overloads would be called depending on T, the implementation of Foo wouldn't be the same for all T any more.

dtb
+1 awesome explanation.
Alex Bagnolini
What's the point of the generic T? Why not just type the data parameter as object and test if (data is int)?
Jamie Ide
That makes alot of sense when thinking about it from the compilers perspective. Though I have a vague memory C++ could accomplish this somehow, though I couldn't get it working now. I'll edit my question a bit to explain a further problem that prevents a workaround to this, I hope you can explain that as well, though I will accept this answer :)
Runeborg
I would've expected it to work more similarly to C++ templates since the information about the call to Foo is available at compile time I had expected it to call Foo<int> when an int is passed and Foo<string> when a string passed which would then in turn call SubFoo(someInt) and SubFoo(someString) like C++ does as C++ templates is compile time resolved as well.
Runeborg
Jamie: Thinking like that you could as well scrap generics, since you can always replace it with object and type casting. But then you scrap type safety and you get a performance loss as well.
Runeborg
A: 

If you want to take advantage of compile-time overload resolution you may as well extend the interface with a method that takes an int:

public interface IStorage
{
    void Store<T>(T data);
}

public interface IIntStorage: IStorage
{
    void Store(int data);
}

public class Storage : IIntStorage
{
    public void Store<T>(T data)
    {
        Console.WriteLine("Generic");
    }

    public void Store(int data)
    {
        Console.WriteLine("Specific");
    }
}

Now if you call Store(1) through the IIntStorage interface it will use the specialized method (similar to how you called Storage's method directly), but if you call it through IStorage it will still use the generic version.

finnw
I wanted a common interface and not a specialized class for each type :)
Runeborg