I'm a .NET guy, so let me first assert my understanding of a few Java concepts - correct me if I'm wrong.
Java Generics support the concept of bounded wildcards:
class GenericClass< ? extends IInterface> { ... }
...which is similar to the .NET where
restriction:
class GenericClass<T> where T: IInterface { ... }
Java's Class
class describes a type, and is roughly equivalent to .NET Type
class
So far, so good. But I can't find a close enough equivalence to the Java genericly typed Class<T>
where T is a bounded wildcard. This basically imposes a restriction on the types that the Class
represents.
Let me give an example in Java.
String custSortclassName = GetClassName(); //only known at runtime,
// e.g. it can come from a config file
Class<? extends IExternalSort> customClass
= Class.forName("MyExternalSort")
.asSubclass(IExternalSort.class); //this checks for correctness
IExternalSort impl = customClass.newInstance(); //look ma', no casting!
The closest I could get in .NET is something like this:
String custSortclassName = GetClassName(); //only known at runtime,
// e.g. it can come from a config file
Assembly assy = GetAssembly(); //unimportant
Type customClass = assy.GetType(custSortclassName);
if(!customClass.IsSubclassOf(typeof(IExternalSort))){
throw new InvalidOperationException(...);
}
IExternalSort impl = (IExternalSort)Activator.CreateInstance(customClass);
The Java version looks cleaner to me. Is there a way to improve the .NET counterpart ?
Using extension methods & a custom wrapper class for System.Type
, you can get pretty close to the Java syntax.
NOTE: Type.IsSubclassOf
cannot be used to test if a type implements an interface - see the linked documentation on MSDN. One can use Type.IsAssignableFrom
instead - see the code below.
using System;
class Type<T>
{
readonly Type type;
public Type(Type type)
{
// Check for the subtyping relation
if (!typeof(T).IsAssignableFrom(type))
throw new ArgumentException("The passed type must be a subtype of " + typeof(T).Name, "type");
this.type = type;
}
public Type UnderlyingType
{
get { return this.type; }
}
}
static class TypeExtensions
{
public static Type<T> AsSubclass<T>(this System.Type type)
{
return new Type<T>(type);
}
}
// This class can be expanded if needed
static class TypeWrapperExtensions
{
public static T CreateInstance<T>(this Type<T> type)
{
return (T)Activator.CreateInstance(type.UnderlyingType);
}
}
(Should only be used in production code after the performance has been evaluated. Could be improved by using a (concurrent!) cache dictionary ConcurrentDictionary<System.Type, IType<object>
)
Using Covariant type parameters
, a feature introduced with C# 4.0, and an additional type interface IType<out T>
, which Type<T>
implements, one could make things like the following possible:
// IExternalSortExtended is a fictional interface derived from IExternalSort
IType<IExternalSortExtended> extendedSort = ...
IType<IExternalSort> externalSort = extendedSort; // No casting here, too.
One could even do:
using System;
interface IType<out T>
{
Type UnderlyingType { get; }
}
static class TypeExtensions
{
private class Type<T> : IType<T>
{
public Type UnderlyingType
{
get { return typeof(T); }
}
}
public static IType<T> AsSubclass<T>(this System.Type type)
{
return (IType<T>)Activator.CreateInstance(
typeof(Type<>).MakeGenericType(type)
);
}
}
static class TypeWrapperExtensions
{
public static T CreateInstance<T>(this IType<T> type)
{
return (T)Activator.CreateInstance(type.UnderlyingType);
}
}
So that one can (explicitly) cast between unrelated interfaces InterfaceA
and InterfaceB
like:
var x = typeof(ConcreteAB).AsSubclass<InterfaceA>();
var y = (IType<InterfaceB>)x;
but that kinda defeats the purpose of the exercise.
C# generics is declaration-site variance, the variance of a type parameter is fixed.
Java is use-site variance, so once we have a declaration List<E>
, we can use it 3 ways
List<Number> // invariant, read/write
List<+Number> // covariant, read only
List<-NUmber> // contravariant, write only
There are pros and cons to both approaches. The use-site approach is apparently more powerful, though it gained the reputation as being too difficult to programmers. I think it is actually pretty easy to grasp
List<Integer> integers = ...;
List<+Number> numbers = integers; // covariant
Unfortunately, Java invented an absolutely hideous syntax,
List<? extends Number> // i.e. List<+Number>
once your code has several of these it becomes really ugly. You have to learn to get over it.
Now, in the declaration-site camp, how do we achieve 3 variances on the same class? By having more types - a ReadOnlyList<out E>
, a WriteOnlyList<in E>
, and a List<E>
extending both. This is not too bad, and one might say it's a better design. But it may become ugly if there are more type parameters. And if the designer of a class did not anticipate it being used variantly, the users of the class have no way to use it variantly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With