0

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.

seangwright
  • 17,245
  • 6
  • 42
  • 54
  • 2
    You are adding constraints on your generics to specific concrete classes. What is the reasoning behind this? – David L Feb 20 '14 at 22:32
  • 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 Feb 20 '14 at 22:58

2 Answers2

1
  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'.

MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263
  • When I make this change and try to create my Config ( Config = new SpecialTaxServiceProviderConfig(paraA, ...); ) in the constructor of my SpecialTaxServiceProvider class, VisualStudio tells me "Cannot implicitly convert type 'SpecialTaxServiceProviderConfig' to 'T'" If I cast as type T ( Config = (T)new SpecialTaxServiceProviderConfig(paramA, ...); ) the error is gone, but I'm not sure if this is the right way to code this – seangwright Feb 20 '14 at 23:37
  • No, it's not the right way. It's like trying to upcast `object` to `string` without knowing it will actually be a `string` instance. What are you trying to achieve? – MarcinJuraszek Feb 20 '14 at 23:39
  • I want the type of my Config property and the return type of my GetTaxes method (both in the Provider classes) to be definable as specific as I choose. If class B inherits from class A (class B : A where these types are defined as concrete classes then any classes that derive from class B must derive from a B where its types are at least as specific in the same hierarchy chain (class C : B – seangwright Feb 21 '14 at 00:44
0

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

Community
  • 1
  • 1
seangwright
  • 17,245
  • 6
  • 42
  • 54