Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net equivalent for Java typed Class<>?

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 ?

like image 533
Cristian Diaconescu Avatar asked Jan 09 '13 14:01

Cristian Diaconescu


2 Answers

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);
    }
}

Further improvements using interface variance

(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.

like image 84
cr7pt0gr4ph7 Avatar answered Oct 19 '22 21:10

cr7pt0gr4ph7


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.

like image 42
irreputable Avatar answered Oct 19 '22 21:10

irreputable