Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

It's possible pass subclass of superclass in <T>?

I have a class named GenericDao

internal class GenericDao<T> : IGenericDao<T> {
}

The two class of objects:

public class Empresa {
}

public class Assessoria : Empresa {
}

And i have one EmpresaDao:

public class EmpresaDao {

    private GenericDao<Empresa> parent { get; set; }

    public EmpresaDao() {
        this.parent = new GenericDao<Empresa>();
    }
}

How to instantiate the GenericDao using the subclass Assessoria? I do something like this, but not work:

public class EmpresaDao {

    private GenericDao<Empresa> parent { get; set; }

    public EmpresaDao(Type type) {
        if (type == typeof(Assessoria)) {
            this.parent = new GenericDao<Assessoria>();
        } else {
            this.parent = new GenericDao<Empresa>();
        }
    }
}
like image 770
Andrey Avatar asked Dec 01 '25 06:12

Andrey


2 Answers

In short, you can't, really. However, you can cheat a little if you use a base interface that is not generic, or you use C# 4 and use a base interface that is generic, but with a covariant or contravariant (depending on need) type parameter. For the first case:

interface IGenericDaoBase {
}

interface IGenericDao<T> : IGenericDaoBase {
}

public class EmpresaDao {
    private IGenericDaoBase parent { get; set; }
    public EmpresaDao(Type type) {
        // same as before
    }
}

Admittedly, it might be better to rethink your design. Perhaps EmpresaDao can take a generic parameter itself, which would be used like so:

public class EmpresaDao<T> where T : Empresa {
    private GenericDao<T> parent { get; set; }
    public EmpresaDao() {
        this.parent = new GenericDao<T>();
    }
}

EDIT: In fact, the more I think about it, the more I believe this latter solution is the way to go. The type parameter in the constructor is fulfilling the same role as the type parameter on the class signature. So you won't have to change the calling code much, except to pass in a generic parameter instead of a Type object.

like image 195
siride Avatar answered Dec 03 '25 19:12

siride


It is a good thing that your try don't work, you would introduce a bug if it did.

Suppose I have variables a, b both of type EmpresaDao. a is initilized with a Empresa parent and b is initialized with a Assessoria parent. Since a and b are of the same type, it should be possible to use one in place of the other everywhere. Suppose Assessoria but not Empresa has a method assess(). But you expect b.parent to be Assessoria so you want to call b.parent.assess() but you cannot call a.parent.assess() Which means a and b should not be of the same type in the first place.

The solution depends on whether you will ever call .parent.assess() :

a) If you will never call .parent.assess() within EmpresaDao class, let compile time type of the parent always be Empresa. Here is a solution :

public class EmpresaDao
{
    private Empresa parent {get; set; }
    public EmpresaDao(Func<Empresa> parentConstructor)
    {
        this.parent = parentConstructor();    
    }
}    
static main()
{
    var withEmpresaParent = new EmpresaDao(() => new Empresa());
    var withAssessoriaParent = new EmpresaDao(() => new Assessoria());
    ..
}

b) You will sometimes call .parent.assess() within EmpresaDao class. Then you should make the EmpresaDao generic, as @siride said:

public class EmpresaDao<T> where T : Empresa
{
    private T parent {get; set;}
}

However, it is still the case that you will have to make run time checks on parent before calling .parent.assess() Which means there is still something wrong in your design. But there is not enough information to decide what. Maybe .assess() method should be private and not to be called from outside (i.e. Assessoria should be a decorator on Empresa: subclass but with the same interface) Maybe "Empresa holding EmpresaDao" and "Assessoria holding EmpresaDao" should be two different classes. (implementing the same interface, probably)

Edit: Now I realize that, in my solution I mistakenly made the type of parent Empresa or Assessoria instead of GenericDao or GenericDao. I believe my main is still valid though.

like image 43
Ali Ferhat Avatar answered Dec 03 '25 19:12

Ali Ferhat



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!