views:

198

answers:

4

Perhaps this is a simple newbie C# question, but so be it---it will be a fresh break from my other questions, which are so difficult that no one knows the answer to them. :)

Let's say I have a generic type in C#:

Thing<T>

And let's say I want to make a thing using a static factory method. In Java, this is no problem:

public static <T> Thing<T> createThing()
{
  return flag ? new Thing<Integer>(5) : new Thing<String>("hello");
}

How do I do this in C#? Thanks.

A: 
        public static Thing<T> CreateThing<T>() 
        {
            return new Thing<T>();
        }
Cookey
@Adam Robinson. The above is perfectly valid with regards to the `new` constraint. You only need it when you do `new T()`, `new Thing<T>()` does not require `new` constraint on `T`. Having said that, this does not resolve the issue that the OP is having.
Igor Zevaka
Indeed new T is different from Thing<T>, the above example is merely a suggestion.
Cookey
+2  A: 

If you want to return an instance of a templated class using one of many different template arguments, one way to do it is with an abstract base (or an interface):

abstract class UntypedThing { }
class Thing<T> : UntypedThing
{
    public Thing(T t) { }
}

class Foo
{
    public static UntypedThing createThing(bool flag)
    {
        if (flag)
            return new Thing<int>(5);
        else return new Thing<String>("hello");
    }
}

The UntypedThing class would contain as much code as possible that does not rely on the template type. The Thing class would ideally only contain code that relies on the template type. The factory class Foo always returns the former.

Reinderien
To be clear, I think the answer is that C# does not allow this sort of thing (even though Java does) because of C#'s limitations on covariant generics and the lack of C# generic wildcards; but Reinderien's answer is the most reasonable workaround. Cheers!
Garret Wilson
+1  A: 

You can in theory use reflection to build up the correct generic type, but it will be pretty useless to you as at some point you will need to upcast it to a less specific type.

public class ThingFactory  {
    public object Create(bool flag) {
        Type outputType = null;
        if(flag) {
            outputType = typeof(string);
        } else {
            outputType = typeof(int);
        }
        return Activator.CreateInstance(typeof(Thing<>).MakeGenericType(outputType));
    }
}

As you can see, the value of doing this is about zero as you will need to cast the return type to the type you want, meaning that the logic to determine it needs to live outside the Create method.

I would use Reinderien's method and have a non-generic base. This is the most sane and idiomatic approach.

Igor Zevaka
+1  A: 

Oh, the trouble I get myself in when I simply try to do something simple.

It turns out that C# 4 allows this sort of covariance---sort of. First, I have to make Thing an interface and specify the "out" generic parameter:

public interface Thing<out T> {...}

But if I do certain things, C# won't let me use covariance. For example, if I try to return T from the interface:

public interface Thing<out T>
{
  public T GetT();

Even if I manage to get covariance with Thing, what do I do with it?

Thing<object> thing=createThing();

The compiler tells me that the type cannot be inferred from usage.

Let's say I say screw the whole T thing and make the factory method return Thing of type object:

public static Thing<object> createThing() {...}

Fine, but now where do I put it?

IList<Thing<object>> list=new List<Thing<object>>();
Thing<object> thing=createThing();
list.Add(thing);

Yes, I have to say that this is a list of Thing with T of type Object, because C# has no wildcard type.

If this were Java, I'd simply say:

public class Thing<T> {...}

public static <T> Thing<T> createThing() {...}

List<?> things=new ArrayList<Thing<?>>();
Thing<?> thing=createThing();
things.add(thing);

If I wanted extra safety by saying that T had to be of a special type, I'd say:

public static <T extends MyBaseType> Thing<T> createThing() {...}

List<? extends MyBaseType> things=new ArrayList<Thing<? extends MyBaseType>>();
Thing<? extends MyBaseType> thing=createThing();
things.add(thing);

Then I'd figure out what T is later, when I had more information.

This all seems to come down to incomplete generic covariance in C# coupled with the lack of C# generic wildcards. (I still maintain it isn't an erasure issue.)

So what do I do? The only simple thing to do seems to follow Reinderien's answer and split out a non-generic base class.

(I wonder if in this non-generic base class I could have object getValue() and then use covariance in the subclass to return T getValue()? Ack, I'm tired of this---I'll leave that for another day.)

Garret Wilson
What you're seeking is not covariance. The ability to specify a wildcard is made possible *only* by type erasure. A generic type without any of its generic arguments specified as *something* simply does not exist within the .NET runtime. Even with covariance and contravariance, you must specify a type that the compiler can use to compare to the specified type in order to determine compatibility. It's not that it's an incomplete implementation, it's that what you're asking for simply isn't co/contra-variance at all.
Adam Robinson
@Adam, I'm trying to understand here. I can have a method with a signature returning interface Thing<object>. Within that method, I can return Thing<int> or Thing<string>. If I understand correctly, this works now with C# 4. How is that different than Java having Thing<?> (which is short for Thing<? extends object>) in the signature? Isn't the common generic type here "object"?
Garret Wilson
@Garret: Your specific example is incorrect (variance applies only to reference types, not to value types like `int`), though the general sentiment is right. Yes, you could use `Thing<object>` as a safe container for any reference type, assuming that `T` is marked appropriately. You can't, however, safely downcast to a more specific `Thing`.
Adam Robinson