Search code examples
c#generics

Using an abstract class with generic method as a property in another class


I have an abstract class like this:

public abstract class ResourceFilter<TFilter> : SomeOtherClass
{
    public static readonly ResourceFilter<ActorTypeEnum> ActorType = new ActorTypeResourceFilter();

    protected ResourceFilter(string name, string friendlyName)
        : base(name, friendlyName)
    {
    }

    public abstract string GetFilterDisplay<T>(T filter);
    public abstract string GetFilterValue<T>(T filter);
    public virtual string GetComparisonValue<T>(T compare)
    {
        return Value + "_" + GetFilterValue(compare);
    }
    public abstract TFilter GetDefaultFilter();
    protected TFilter TypeCheck<T>(T value)
    {
        if (value is null)
            throw new ArgumentNullException(nameof(value));

        if (value is TFilter excpectedType)
            return excpectedType;

        throw new FriendlyException($"Type {value.GetType()} is not valid for this Resource Filter");
    }
}

Which has a concretion like this:

public sealed class ActorTypeResourceFilter : ResourceFilter<ActorTypeEnum>
{

    internal ActorTypeResourceFilter()
        : base(nameof(ActorType), "Actor Type")
    {
    }

    public override string GetFilterDisplay<T>(T filter)
    {
        ActorTypeEnum actorType = TypeCheck(filter);

        return actorType.FriendlyName;
    }

    public override string GetFilterValue<T>(T filter)
    {
        ActorTypeEnum actorType = TypeCheck(filter);

        return actorType.Name;
    }

    public override ActorTypeEnum GetDefaultFilter()
    {
        return ActorTypeEnum.User;
    }
}

I want to be able to use the abstract class as the type on a class:

public class ClassToApplyResourceFilter
{
    public ResourceFilter ResourceFilter {get; private set;}
}

I'm unable to use ResourceFilter as the type without providing TFilter, which defeats the point of making it generic


Solution

  • If you want to have generic type for a field, you have to make generic containing class as well:

    public class ClassToApplyResourceFilter<TFilter>
    {
        public ResourceFilter<TFilter> ResourceFilter { get; private set; }
    }
    

    You could use Covariance and Contravariance (C#) to allow more flexible approach, but for that you need to define interface as base for abstract class, as only interface/delegate generic type parameters can be specified as variant.