简体   繁体   中英

C# Generics and polymorphism: an oxymoron?

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?

As far as I can see, consuming code doesn't need specifics of generic class (ie, 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.

Eg:

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 T s, but consuming code won't change.

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.

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

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

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.

public Class ReflectionReport<T> 
{
    // This class uses Reflection to produce column-based grids for reporting.  
    // However, you need a type in order to know what the columns are. 
}

... so, in another class you have...

public Class ReflectionReportProject {
    ReflectionReport<Thang> thangReport = new ReflectionReport<Thang>(); 
    ReflectionReport<Thong> thongReport = new ReflectionReport<Thong>(); 



    ... some more stuff ...

    // Now, you want to pass off all of the reports to be processed at one time....
    public ReflectionReport<????>[] ProvideReports() 
    {
        return new ReflectionReport<????>[] { thangReport, thongReport } ;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM