Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# generic interface specialization

Tags:

c#

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?

like image 609
Runeborg Avatar asked Feb 09 '10 12:02

Runeborg


3 Answers

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.

like image 125
dtb Avatar answered Nov 08 '22 08:11

dtb


Why Generic code-based specialization make a lot of sense in real world and in particular, in extension methods ?

I will take an example on collections because evrybody kowns more or less .NET collections.

I will take the simple example of the .Last(this IEnumerable<<T>> coll) extension method. In .NET Framework, this method use in-code type specialization.

First, concerning the benefit of type specialization, this example is quite clear. Some enumerable collections need to scan the whole collection and return the last element, array based one need only to return the last allocated element of the array, many linked list have a pointer to the last element... So implementing a generic with type specialization can make the .Last() method by far more efficient.

Second because this method is static, having many implementations for each collection type or interfaces wouldn't solve the problem of right method selection. In effect, selection of the right method is done at compile time based on apparent type of coll object. If you imagine, you want to apply consecutive extensions methods on a List<<T>>, the first one may not need many per collection type specialized implementations and use a single one based on IEnumerable<<T>>. So even if we have a .Last(this List<<T>> coll), the first non specialized extension method will return a IEnumerable<<T>> and the specialized .Last(this List<<T>> coll) will not be used for the List<<T>>.

So if your code use external assemblies (even .NET Framework itself), if you have to provide a solution in two weeks to a complex architectural problem ... you leave the domain of the perfection to enter in the real world. And generic type specialization become an not to ignore option.

like image 35
Renaud Bancel Avatar answered Nov 08 '22 10:11

Renaud Bancel


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.

like image 44
finnw Avatar answered Nov 08 '22 10:11

finnw