0
votes

Assume the following classes

// What I have created ...

public abstract class TaxServiceProvider<T, S>
    where T : TaxServiceProviderConfig
    where S : TaxServiceInfo
{
    protected T Config { get; set; }

    public abstract S GetTax(int zipCode);
}

public abstract class TaxServiceInfo { ... }
public abstract class TaxServiceProviderConfig { ... }

// What I want to create ...

public class SpecialTaxServiceProvider<T, S> : TaxServiceProvider<SpecialTaxServiceProviderConfig, SpecialTaxServiceInfo>
    where T : SpecialTaxServiceProviderConfig
    where S : SpecialTaxServiceInfo
{ ... }
public class SpecialTaxServiceInfo : TaxServiceInfo { ... }
public class SpecialTaxServiceProviderConfig : TaxServiceProviderConfig { ... }

where TaxServiceInfo and TaxServiceProviderConfig are used to support the TaxServiceProvider class.

I want to create a derived class SpecialTaxServiceProvder (non-abstract) from TaxServiceProvider that is also generic in the same way that TaxServiceProvider is and takes SpecialTaxServiceInfo and SpecialTaxServiceProviderConfig as the types.

I want to implement GetTax and Config in SpecialTaxServiceProvider so that GetTax returns type SpecialTaxServiceInfo and Config is of type SpecialTaxServiceProviderConfig

I would then create an additional class derived from SpecialTaxServiceProvider and classes derived from SpecialTaxServiceInfo and SpecialTaxServiceProviderConfig

public class A_SpecialTaxServiceProvider : SpecialTaxServiceProvider<A_SpecialTaxServiceProviderConfig, A_SpecialTaxServiceInfo>
{ ... }
public class A_SpecialTaxServiceProviderConfig : SpecialTaxServiceProviderConfig { ... }
public class A_SpecialTaxServiceInfo : SpecialTaxServiceInfo { ... }

where GetTax for this class returns type A_SpecialTaxServiceInfo and the Config for this class is of type A_SpecialTaxServiceProviderConfig

I've looked into covariance in C# and the syntax for generic typed classes but I'm not sure if what I'm trying to do is impossible in the language or I just don't know the proper way to set it up.

2
You are adding constraints on your generics to specific concrete classes. What is the reasoning behind this? - David L
I want any derived classes to be constrained to using classes at the same level of inheritance. If a class inherits from SpecialTaxServiceProvider, I want the Config/Info classes it uses to be from that same (or more specific) 'group' or 'level' of the inheritance hierarchy - seangwright

2 Answers

1
votes
  1. Change SpecialTaxServiceProvider and pass T and S to TaxServiceProvider:

    public class SpecialTaxServiceProvider<T, S> : TaxServiceProvider<T, S>
        where T : SpecialTaxServiceProviderConfig
        where S : SpecialTaxServiceInfo
    {
        public override S GetTax(int zipCode)
        {
            return null;
        }
    }
    
  2. Implement A_SpecialTaxServiceProvider and override GetTax:

    public class A_SpecialTaxServiceProvider : SpecialTaxServiceProvider<A_SpecialTaxServiceProviderConfig, A_SpecialTaxServiceInfo>
    {
        public override A_SpecialTaxServiceInfo GetTax(int zipCode)
        {
            return null;
        }
    }
    

It prevents you from creating

public class A_SpecialTaxServiceProvider : SpecialTaxServiceProvider<A_SpecialTaxServiceProviderConfig, TaxServiceInfo>

with following error:

The type 'Project.TaxServiceInfo' cannot be used as type parameter 'S' in the generic type or method 'Project.SpecialTaxServiceProvider'. There is no implicit reference conversion from 'Project.TaxServiceInfo' to 'Project.SpecialTaxServiceInfo'.

0
votes

I decided to use function delegates in my constructors to allow me to pass in factory methods to create my generic types.

Here is what I ended up with

// Tier 1 of my class hierarchy
public abstract class TaxServiceProvider<C, I>
    where C : TaxServiceProviderConfig
    where I : TaxServiceInfo
{
    protected C Config { get; set; }

    public abstract I GetTax(int zipCode);
}

public abstract class TaxServiceInfo {
    public TaxServiceInfo(string param1, string param2, int param3, ect...) {
        ...
    }
}

public abstract class TaxServiceProviderConfig { ... }


// Tier 2 of my class hierarchy
public class DerivedTaxServiceProvider<C, I> : TaxServiceProvider<C, I>
    where C : DerivedTaxServiceProviderConfig
    where I : DerivedTaxServiceInfo
{
    protected Func<S, string, string, int, ect...> Factory;

    public DerivedTaxServiceProvider (C config, Func<I, string, string, int, ect...> factory) {
        Config = config;
        Factory = factory;
    }

    public override I GetTax(int zipCode) {
        ...
        I taxServiceInfo = Factory("param1", "param2", 3, ect...);
        ...
        return I;
    }
}

public class DerivedTaxServiceInfo : TaxServiceInfo {
    public DerivedTaxServiceInfo(string param1, string param2, int param3, ect...) 
        : base(param1, param2, param3, ect...) 
    { ... }
}

public class DerivedTaxServiceProviderConfig : TaxServiceProviderConfig { ... }

// Tier 3 of my class hierarchy
public class ConcreteTaxServiceProvider : DerivedTaxServiceProvider<ConcreteTaxServiceProviderConfig, ConcreteTaxServiceInfo> {
    public ConcreteTaxServiceProvider(ConcreteTaxServiceProviderConfig config, Func<ConcreteTaxServiceInfo, string, string, int, ect...> factory) {
        Config = config;
        Factory = factory;
    }

    public override ConcreteTaxServiceInfo GetTax(int zipCode) {
        return base.GetTax(zipCode);
    }
}

public class ConcreteTaxServiceInfo : DerivedTaxServiceInfo {
    public ConcreteTaxServiceInfo(string param1, string param2, int param3, ect...) 
        : base(param1, param2, param3, ect...) 
    { ... }

    public static ConcreteTaxServiceInfo CreateConcreteTaxServiceInfo(string param1, string param2, int param3, ect...) {
        return new ConcreteTaxServiceInfo(param1, param2, param3, etc...);
    }
}

public class ConcreteTaxServiceProviderConfig : DerivedTaxServiceProviderConfig { ... }

// Implementation of my class hierarchies
public void method() {
    ConcreteTaxServiceProviderConfig() config = new ConcreteTaxServiceProviderConfig();

    ConcreteTaxServiceProvider provider = new ConcreteTaxServiceProvider(config, ConcreteTaxServiceInfo.CreateConcreteTaxServiceInfo);

    ConcreteTaxServiceInfo serviceInfo = provider.GetTax(99939);
}

So basically I have 2 levels of generic classes. The 2nd level Provider class overrides the base abstract method for "GetTax", but I didn't want it to return a concrete type because then I couldn't cleanly call that method in a class that inherited from it. I would have had to cast my derived ServiceInfo into a ConcreteServiceInfo type when calling "GetTax" in the concrete (3rd level) Provider.

As long as I have a constructor in an Info class that matches my delegate, no matter at what level of subclassing (below the 2nd level) that Info class is, I can feed it into my provider and use the 2nd level's Provider class GetTax method.

This factory method can look kind of ugly in the parameter lists for the Provider constructors and creating static methods of the Info classes seems awkward, but it does the trick!

Here is an SO question I consulted to come up with this solution : C# generics problem - newing up the generic type with parameters in the constructor