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 tha
You can do it by introducing additional type information (e.g. implementing an interface). Here is an example.
// no need to modify the original interface
public interface IStorage
{
void Store<T>(T data);
}
Implementation with specialization based on a generic interface
public class Storage : IStorage,
Storage.ISpecializedFor<int>,
Storage.ISpecializedFor<double>
{
// a private interface to provide additional type info
private interface ISpecializedFor<T>
{
void Store(T data);
}
public void Store<T>(T data)
{
// resolve specialization
if (this is ISpecializedFor<T> specialized)
{
specialized.Store(data);
}
else
{
// unspecialized implementation goes here
Console.WriteLine("Unspecialized");
}
}
// specialized implementation
void ISpecializedFor<int>.Store(int data)
{
Console.WriteLine("Specialized for int");
}
void ISpecializedFor<double>.Store(double data)
{
Console.WriteLine("Specialized for double");
}
}
Results
void Main()
{
IStorage storage = new Storage();
storage.Store("hello"); // prints "Unspecialized"
storage.Store(42); // prints "Specialized for int"
storage.Store(1.0); // prints "Specialized for double"
}
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.
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.
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.
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.
Because C# generics are runtime templates in some circumstances you should use runtime specialization. For example, in generic static methods, inheritance and interfaces aren't usable. If you want to specialize generic static methods - in particular extension methods - you have to detect the type in code with constructs like :
if (typeof(T)==typeof(bool))
For specialization of reference types (like string for example), and an argument T data, you would prefere:
string s = data as string; if (s!=null)
In this example, a problem come from conversion between T and bool in specialized code : You know that T is bool but the language doesn't allow conversion between those types. The solution come from the object type: an object can be casted to any type (conversion is checked at runtime and not at compile time in this case). so if you have
T data;
you can write:
bool b=(bool)(object)data; data=(T)(object)b;
This isn't perfect: If type equality is quite fast, in some circumstances you have to test if T is a derived type of a specified type (a little longer). And when T is a value type like bool, cast to object and then back to type mean value type boxing/unboxing and runtime type check for reference types. Runtime optimiser can remove these non necessary steps but I cannot say if they do so.
Depending on the usage of your static method, remember that you can apply where T: ... restrictions on parametrized types. And that default(T) return false for boolean, zero for numeric base types and null for reference types.
Runtime specialization implies an additional test steps and boxing/unboxing/runtime type checks, so it isn't the panacea but allows too specialize generic methods in an acceptable time in many circumstances: For long operation (in particular for optimization) or when hiding or grouping of types complexity management is more important than performances.