Search code examples
c#inheritanceabstract

How to specify parameter type as derived class type in override of abstract function where parameter is type of base class?


I would like to store SpecificResult instances (and other types derived from Result) in a List<Result>. Each derived type may contain different properties. I would like to have a virtual base class method LimitsPassed on Result that checks if those specific properties meet some criteria (e.g. values of properties from the Limits derived instances that are passed as parameter to the method). Different classes derived from Results should receive different types of Limits.

I've tried to override the LimitsPassed method with a more-derived SpecificLimits parameter.

Obviously the code shown here doesn't compile, because type in derived class does not match base class definition.

What are possible solutions for that?

public abstract class Result
{
    public abstract bool LimitsPassed(Limits limits)
}

public class SpecificResult : Result
{
    public double MeasureA { get; set; }
    public string MeasureB { get; set; }

    public override bool LimitsPassed(SpecificLimits limits) 
    {
        //specify parameter base type to particular type of derived class

        return this.MeasureA >= limits.MeasureA_min
                && this.MeasureA <= limits.MeasureA_max
                && this.MeasureB == limits.MeasureB_master;
    }
}

public abstract class Limits
{
}

public class SpecificLimits : Limits
{
    public double MeasureA_min { get; set; }
    public double MeasureA_max { get; set; }

    public string MeasureB_master { get; set; }
}

Solution

  • From the comments and your question I don't see anything that is really common to Limits, so maybe we can have a base Result class that looks like this (I've made LimitsPassed return null instead of throwing exceptions for invalid limit types but that's a matter of choice of course)

    public abstract class Result {
        // can be bool but then we have to throw exceptions
        // when invalid Limits type
        public abstract bool? LimitsPassed(object limits);
    
        // helps us when we have List<Result>
        // so we can filter the results that support the limit
        // check for certain Limit Types
        public abstract Type[] AllowedLimitTypes { get; }
    }
    

    this will handle the cases where we have multiple possible limit types in our derived classes.

    We can also have a convenience generic version for single limit type that is easy to override

    public abstract class Result<TLimit> : Result {
        // caching
        static Type[] s_types = new Type[] { typeof(TLimit) };
        public override Type[] AllowedLimitTypes => s_types;
    
        // default implementation so we don't have to do it every time in our derived classes
        // sealed override doesn't allow derived classes to break the contract 
        // of having just a single limit as generic parameter 
        // (for example by including checks for multiple limits types in this method)
        // those classes should derive from the non-generic Result instead
        public sealed override bool? LimitsPassed(object limits) {
            if (limits is TLimit tLimit) return LimitsPassed(tLimit);
            return null;
        }
        public abstract bool LimitsPassed(TLimit limits);
    }
    
    public class SpecificResult : Result<SpecificLimits> {
        public double MeasureA { get; set; }
        public string MeasureB { get; set; }
    
        public override bool LimitsPassed(SpecificLimits limits) {
            return this.MeasureA >= limits.MeasureA_min
                    && this.MeasureA <= limits.MeasureA_max
                    && this.MeasureB == limits.MeasureB_master;
        }
    }
    

    For the multiple limits types we have a bit more work:

    public class SpecificResult2 : Result {
    
        Type[] _types = new Type[] { typeof(SpecificLimits), typeof(SpecificLimits2) };
        public override Type[] AllowedLimitTypes => _types;
    
        public override bool? LimitsPassed(object limits) {
            return limits switch {
                SpecificLimits specLimits => LimitsPassed(specLimits),
                SpecificLimits2 specLimits2 => LimitsPassed(specLimits2),
                _ => null
            };
        }
    
        public bool LimitsPassed(SpecificLimits limits) {
            // implementation
            return true;
        }
    
        public bool LimitsPassed(SpecificLimits2 limits) {
            // implementation
            return true;
        }
    }
    

    We can also have generic convenience classes for more common cases - two or three TLimits:

    public abstract class Result<TLimit1,TLimit2> 
    public abstract class Result<TLimit1,TLimit2,TLimit3>