15

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 ?

Cristian Diaconescu
  • 34,633
  • 32
  • 143
  • 233
  • 1
    This may be a little bit too easy, but wouldn't it be possible to simply add the `new()` constraint to the where restriction? Something like `MyExternalSort : IExternalSort where T : IExternalSort, new()` and then use `var impl = new T();`? Personally I would use the factory pattern to create instances of a types implementing the same interface in such scenarios. – Carsten Jan 09 '13 at 14:19
  • I don't know much about Java generics, but I do know that they're implemented differently (no JVM support), and support a number of things that .NET generics do not. So don't expect to find a straight "equivalent" for everything – jalf Jan 09 '13 at 15:05
  • @Aschratt I don't see how this is relevant to my question. This trick could be useful if all the type information is known at compile time, but the type MyExternalSort is not known until runtime - it may well be implemented by a client of my sorting library, and is only specified by name. – Cristian Diaconescu Jan 09 '13 at 15:06
  • @jalf I know about type erasure, so I'm certainly not expecting 1:1 mapping. I'm just hoping for some improvement over my currently not-so-pretty implementation. – Cristian Diaconescu Jan 09 '13 at 15:08
  • @Cristi Diaconescu: the specialized type does not need to be known on compile time. It simply needs to fit your constraint (inherit from IExternalSort, parameterless constructor) on compile time, so you can also provide some parameter as IExternalSort instance, when you do not know it's exact type. – Carsten Jan 09 '13 at 15:29

4 Answers4

2

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.

cr7pt0gr4ph7
  • 1,244
  • 13
  • 19
1

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.

irreputable
  • 44,725
  • 9
  • 65
  • 93
  • I think I now understand what your answer is about - although it's more related to [this (newer) question](http://stackoverflow.com/q/14277441/11545) of mine. Maybe you can take a look. – Cristian Diaconescu Jan 11 '13 at 15:14
0

You can get a slightly prettier version using the "as" operator:

String custSortclassName = GetClassName();
Assembly assy = GetAssembly();
Type customClass = assy.GetType(custSortclassName);

IExternalSort impl = Activator.CreateInstance(customClass) as IExternalSort;
if(impl==null) throw new InvalidOperationException(...);

But here I'm creating the instance before checking its type, which may be an issue for you.

Badaro
  • 3,460
  • 1
  • 19
  • 18
  • 1
    The `as` operator also performs a cast. The only difference is, that it does not throw an exception if the cast was not successfull. This doesn't really make it easier. – Carsten Jan 09 '13 at 15:30
0

You can try writing an extension method like the following:

 static class TypeExtension
    {
        public static I NewInstanceOf<I>(this Type t) 
            where  I: class 
        {
            I instance = Activator.CreateInstance(t) as I;
            if (instance == null)
                throw new InvalidOperationException();
            return instance;
        }
    }

Which can then be used in the following manner:

String custSortclassName = GetClassName(); //only known at runtime, 
                                           // e.g. it can come from a config file

Assembly assy = GetAssembly();
Type customClass = assy.GetType(custSortclassName);            

IExternalSort impl = customClass.NewInstanceOf<IExternalSort>();
Hidden_au
  • 83
  • 6