views:

414

answers:

5

I just want to confirm what I've understood about Generics in C#. This has come up in a couple code bases I've worked in where a generic base class is used to create type-safe derived instances. A very simple example of what I'm talking about,

public class SomeClass<T>
{
    public virtual void SomeMethod(){ }
}

public class DeriveFrom :SomeClass<string>
{
    public override void SomeMethod()
    {
        base.SomeMethod();
    }
}

The problem comes up when I then want to use derived instances in a polymorphic way.

public class ClientCode
{
    public void DoSomethingClienty()
    {
        Factory factory = new Factory();
        //Doesn't compile because SomeClass needs a type parameter!
        SomeClass someInstance = factory.Create();

        someInstance.SomeMethod();
    }
}

It seems that once you introduce a Generic into an inheritance hierarchy or interface, you can no longer use that family of classes in a polymorphic way except perhaps internal to itself. Is that true?

+5  A: 

I think you are misunderstanding the point of generics. Generics allows you to generalise a class that requires a type, but doesn't particularly care itself what type that is. For instance, a List<string> is a list of strings, but what would a List be? It's a rather useless concept to have a list of nothing.

Each specialised class (ie, List<string>) is it's own distinct type, and the compiler treats it as such. It is possible to get at the generic type itself (typeof(List<>) for instance), but in most cases it's useless, and you certainly can't make an instance of it.

Matthew Scharley
+6  A: 

As far as I can see, consuming code doesn't need specifics of generic class (i.e., it doesn't depends on what T is). So, why don't you introduce interface that SomeClass<T> will implement, and use instance of this interface.

E.g.:

public interface ISome
{
    void SomeMethod();
}

public class SomeClass<T>: ISome
{
    public virtual void SomeMethod(){ }
}

public void DoSomethingClienty()
{
    Factory factory = new Factory();
    ISome someInstance = factory.Create();

    someInstance.SomeMethod();
}

Now, subclasses of SomeClass<T> can operate differently on different Ts, but consuming code won't change.

elder_george
I think this is the best solution. We extend only the interface the client cares about and we hide the generics under the hood.
Nick Swarr
+2  A: 

I would prefer using abstract class to act as base of all generic types.

public abstract class SomeClass {
            public abstract void SomeMethod();
        }

        public class SomeClass<T> : SomeClass
        {
            public override void SomeMethod() { }            
        }

        public class DeriveFrom<String> : SomeClass<String>
        {
            public override void SomeMethod() { base.SomeMethod(); }            
        }
Amby
Indeed, but how would `Factory` work here?
Jeremy McGee
Have no idea about the implementation of "Factory" mentioned by Nick. But i am quite sure, it can very well be implemented in the abstract non-generic 'SomeClass'.
Amby
A: 

I think what you're looking for is:

  SomeClass(of someType) someInstance = factory(of someType).Create();
or maybe
  SomeClass(of someType) someInstance = factory.Create(of someType)();
or
  SomeClass(of someType) someInstance = factory.Create();

It's possible to have a set of factory classes to create different generic classes, or to have a factory with a generic type parameter to indicate which generic type it should create (note that in either case, the type parameter is the type parameter for the generic class, rather than being the generic class itself). It's also possible to have a factory which is designed to return instances of one particular form of a generic type.

supercat
A: 

It seems that once you introduce a Generic into an inheritance hierarchy or interface, you can no longer use that family of classes in a polymorphic way

Correct, it's quite similar to this situation:

class StringContainer
{
}

class IntContainer
{
}

StringContainer container = new IntContainer(); // fails to compile

but you could do this:

class Container
{
}

class Container<T> : Container
{
}

Container container = new Container<String>(); // OK
Douglas